diff --git a/.config/nextest.toml b/.config/nextest.toml index 56f40c3641..a90da8b186 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,5 +1,5 @@ [profile.default] -retries = { backoff = "exponential", count = 2, delay = "2s", jitter = true } +retries = { backoff = "exponential", count = 2, delay = "3s", jitter = true } slow-timeout = { period = "1m", terminate-after = 3 } [[profile.default.overrides]] diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml index f9c0455ca8..147eb859e6 100644 --- a/.github/ISSUE_TEMPLATE/BUG-FORM.yml +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -30,6 +30,10 @@ body: 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? @@ -49,4 +53,4 @@ body: label: Describe the bug description: Please include relevant Solidity snippets as well if relevant. validations: - required: true + required: true \ No newline at end of file diff --git a/.github/assets/banner.png b/.github/assets/banner.png new file mode 100644 index 0000000000..2a3752b97f Binary files /dev/null and b/.github/assets/banner.png differ diff --git a/.github/assets/build_benchmark_openzeppelin_dark.png b/.github/assets/build_benchmark_openzeppelin_dark.png new file mode 100644 index 0000000000..75d80d127c Binary files /dev/null and b/.github/assets/build_benchmark_openzeppelin_dark.png differ diff --git a/.github/assets/build_benchmark_openzeppelin_light.png b/.github/assets/build_benchmark_openzeppelin_light.png new file mode 100644 index 0000000000..2772a6ab10 Binary files /dev/null and b/.github/assets/build_benchmark_openzeppelin_light.png differ diff --git a/.github/assets/build_benchmark_solady_dark.png b/.github/assets/build_benchmark_solady_dark.png new file mode 100644 index 0000000000..123c51f913 Binary files /dev/null and b/.github/assets/build_benchmark_solady_dark.png differ diff --git a/.github/assets/build_benchmark_solady_light.png b/.github/assets/build_benchmark_solady_light.png new file mode 100644 index 0000000000..b2836d81f7 Binary files /dev/null and b/.github/assets/build_benchmark_solady_light.png differ diff --git a/.github/assets/demo.gif b/.github/assets/demo.gif new file mode 100644 index 0000000000..bebdb8148c Binary files /dev/null and b/.github/assets/demo.gif differ diff --git a/.github/scripts/format.sh b/.github/scripts/format.sh index aefb4c0ea2..9bd1f950fd 100755 --- a/.github/scripts/format.sh +++ b/.github/scripts/format.sh @@ -3,5 +3,5 @@ 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 -shopt -s extglob -cargo run --bin forge -- fmt "$@" $(find testdata -name '*.sol' ! -name Vm.sol) +cargo run --bin forge -- fmt "$@" \ + $(find testdata -name '*.sol' ! -name Vm.sol ! -name console.sol) diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 20c2ce1789..1b76eb1076 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -9,22 +9,9 @@ on: 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 - ``` - -

-
+permissions: + contents: write + pull-requests: write jobs: update: @@ -37,28 +24,28 @@ jobs: with: toolchain: nightly-2024-09-01 - - 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: 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)" + - 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 "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 + 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 }} + - 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 }} diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index e2be193290..615f684e9e 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -15,6 +15,7 @@ concurrency: env: CARGO_TERM_COLOR: always + RUST_BACKTRACE: full jobs: matrices: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82e820a321..fc163ce281 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -158,6 +158,7 @@ jobs: # We diverge from upstream and build with cross as we're building static binaries - name: Build binaries env: + TAG_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} shell: bash run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9cafc77a8..5fca3e888e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ concurrency: env: CARGO_TERM_COLOR: always TARGET_RUST_VERSION: "stable" + RUST_BACKTRACE: full jobs: nextest: diff --git a/.gitignore b/.gitignore index 5b61e32022..9297bbbc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_STORE -/target +/target* out/ snapshots/ out.json diff --git a/Cargo.lock b/Cargo.lock index eebcd7d5c9..4fc7ebad6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1117,6 +1117,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-lossy" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b" +dependencies = [ + "anstyle", +] + [[package]] name = "anstyle-parse" version = "0.2.6" @@ -1135,6 +1144,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anstyle-svg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35" +dependencies = [ + "anstream", + "anstyle", + "anstyle-lossy", + "html-escape", + "unicode-width 0.2.0", +] + [[package]] name = "anstyle-wincon" version = "3.0.6" @@ -1193,7 +1215,7 @@ dependencies = [ "foundry-test-utils", "futures 0.3.31", "hyper 1.5.1", - "itertools 0.13.0", + "itertools 0.14.0", "k256 0.13.4", "op-alloy-consensus", "op-alloy-rpc-types", @@ -1209,10 +1231,9 @@ dependencies = [ "thiserror 2.0.11", "tikv-jemallocator", "tokio", - "tower 0.4.13", + "tower 0.5.1", "tracing", "tracing-subscriber", - "vergen", "yansi", ] @@ -1265,7 +1286,7 @@ dependencies = [ "serde_json", "thiserror 2.0.11", "tokio-util", - "tower-http 0.5.2", + "tower-http", "tracing", ] @@ -1294,7 +1315,7 @@ dependencies = [ "jsonrpsee", "thiserror 1.0.69", "tower 0.4.13", - "tower-http 0.6.2", + "tower-http", "tracing", "zksync_types", "zksync_web3_decl", @@ -2529,7 +2550,7 @@ dependencies = [ "foundry-zksync-core", "futures 0.3.31", "indicatif", - "itertools 0.13.0", + "itertools 0.14.0", "rand 0.8.5", "rayon", "regex", @@ -2542,7 +2563,6 @@ dependencies = [ "tikv-jemallocator", "tokio", "tracing", - "vergen", "yansi", "zksync_types", ] @@ -2607,12 +2627,10 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "alloy-rpc-types", "clap", - "dirs 5.0.1", + "dirs 6.0.0", "eyre", "forge-fmt", - "foundry-block-explorers", "foundry-cli", "foundry-common", "foundry-compilers", @@ -2635,7 +2653,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "vergen", + "walkdir", "yansi", ] @@ -2790,15 +2808,15 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clearscreen" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c" +checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4" dependencies = [ - "nix 0.28.0", + "nix 0.29.0", "terminfo", - "thiserror 1.0.69", - "which 6.0.3", - "winapi", + "thiserror 2.0.11", + "which 7.0.1", + "windows-sys 0.59.0", ] [[package]] @@ -3545,20 +3563,20 @@ dependencies = [ [[package]] name = "dirs" -version = "4.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.3.7", + "dirs-sys 0.4.1", ] [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.4.1", + "dirs-sys 0.5.0", ] [[package]] @@ -3573,25 +3591,26 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", - "redox_users", - "winapi", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", - "windows-sys 0.48.0", + "redox_users 0.5.0", + "windows-sys 0.59.0", ] [[package]] @@ -3601,7 +3620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -3846,6 +3865,12 @@ dependencies = [ "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.5" @@ -4300,7 +4325,7 @@ dependencies = [ "hyper 1.5.1", "indicatif", "inferno", - "itertools 0.13.0", + "itertools 0.14.0", "mockall", "opener", "parking_lot", @@ -4329,10 +4354,9 @@ dependencies = [ "tokio", "toml 0.8.19", "toml_edit 0.22.22", - "tower-http 0.5.2", + "tower-http", "tracing", "tracing-subscriber", - "vergen", "watchexec", "watchexec-events", "watchexec-signals", @@ -4351,7 +4375,7 @@ dependencies = [ "foundry-common", "foundry-compilers", "foundry-config", - "itertools 0.13.0", + "itertools 0.14.0", "mdbook", "rayon", "regex", @@ -4370,7 +4394,7 @@ dependencies = [ "alloy-primitives", "ariadne", "foundry-config", - "itertools 0.13.0", + "itertools 0.14.0", "similar-asserts", "solang-parser", "thiserror 2.0.11", @@ -4416,7 +4440,7 @@ dependencies = [ "foundry-zksync-core", "futures 0.3.31", "indicatif", - "itertools 0.13.0", + "itertools 0.14.0", "parking_lot", "revm-inspectors", "semver 1.0.23", @@ -4485,7 +4509,7 @@ dependencies = [ "foundry-test-utils", "foundry-zksync-compilers", "futures 0.3.31", - "itertools 0.13.0", + "itertools 0.14.0", "regex", "reqwest 0.12.9", "revm-primitives", @@ -4509,9 +4533,9 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faa449506113b4969029da2ac1df3a1b3201bf10c99a4a8e6d684977b80c938" +checksum = "0d7f2a3d90ff85c164c0eb05fdf19b22e68b9190b5449d1aa0eef8314c1874e9" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -4542,7 +4566,6 @@ dependencies = [ "alloy-signer-local 0.9.2", "alloy-sol-types", "base64 0.22.1", - "chrono", "dialoguer", "ecdsa 0.16.9", "eyre", @@ -4557,7 +4580,7 @@ dependencies = [ "foundry-wallets", "foundry-zksync-core", "foundry-zksync-inspectors", - "itertools 0.13.0", + "itertools 0.14.0", "jsonpath_lib", "k256 0.13.4", "memchr", @@ -4573,7 +4596,6 @@ dependencies = [ "thiserror 2.0.11", "toml 0.8.19", "tracing", - "vergen", "walkdir", ] @@ -4625,6 +4647,7 @@ dependencies = [ "foundry-zksync-compilers", "futures 0.3.31", "indicatif", + "itertools 0.14.0", "rayon", "regex", "serde", @@ -4665,6 +4688,8 @@ dependencies = [ "anstream", "anstyle", "async-trait", + "axum", + "chrono", "clap", "comfy-table", "dunce", @@ -4674,7 +4699,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-zksync-compilers", - "itertools 0.13.0", + "itertools 0.14.0", "num-format", "reqwest 0.12.9", "semver 1.0.23", @@ -4684,9 +4709,10 @@ dependencies = [ "terminal_size", "thiserror 2.0.11", "tokio", - "tower 0.4.13", + "tower 0.5.1", "tracing", "url", + "vergen", "walkdir", "yansi", ] @@ -4713,15 +4739,15 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.12.9" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67e3eab56847dcf269eb186226f95874b171e262952cff6c910da36b1469e10" +checksum = "203e96bd596350ec8d9aedfca7eff573e9347e2b2fe50eedfbf78532dabe418e" dependencies = [ "alloy-json-abi", "alloy-primitives", "auto_impl", "derive_more", - "dirs 5.0.1", + "dirs 6.0.0", "dyn-clone", "foundry-compilers-artifacts", "foundry-compilers-core", @@ -4744,15 +4770,15 @@ dependencies = [ "thiserror 2.0.11", "tokio", "tracing", - "winnow 0.6.20", + "winnow 0.7.1", "yansi", ] [[package]] name = "foundry-compilers-artifacts" -version = "0.12.9" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865b00448dc2a5d56bae287c36fa716379ffcdd937aefb7758bd20b62024d234" +checksum = "be8ba2ccf8a4bf7730b2ad2815984c1af8e5b8c492420f1cf1d26a8be29cc9e4" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4760,9 +4786,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.12.9" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668972ba511f80895ea12c75cd12fccd6627c26e64763799d83978b4e0916cae" +checksum = "5889eeb7d6729afbbbb1313b22e8f58aa909fcf38aa6b2f9c9b2443ca0765c59" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4784,9 +4810,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.12.9" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a24f7f2a7458171e055c0cb33272f5eccaefbd96d791d74177d9a1fca048f74" +checksum = "b01048f354b4e98bf5fb4810e0607c87d4b8cc7fe6305a42103ce192e33b2da7" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4799,9 +4825,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.12.9" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8005271a079bc6470c61d4145d2e390a827b1ccbb96abb7b69b088f17ffb95e0" +checksum = "079b80a4f188af9c273b081547d1a95a0a33f782851a99e21aa4eba06e47fa34" dependencies = [ "alloy-primitives", "cfg-if", @@ -4826,7 +4852,7 @@ dependencies = [ "Inflector", "alloy-chains", "alloy-primitives", - "dirs-next", + "dirs 6.0.0", "dunce", "era-solc", "eyre", @@ -4836,7 +4862,7 @@ dependencies = [ "foundry-zksync-compilers", "glob", "globset", - "itertools 0.13.0", + "itertools 0.14.0", "mesc", "number_prefix", "path-slash", @@ -4918,7 +4944,7 @@ dependencies = [ "foundry-common-fmt", "foundry-macros", "foundry-test-utils", - "itertools 0.13.0", + "itertools 0.14.0", ] [[package]] @@ -4945,7 +4971,7 @@ dependencies = [ "foundry-test-utils", "foundry-zksync-core", "futures 0.3.31", - "itertools 0.13.0", + "itertools 0.14.0", "parking_lot", "revm", "revm-inspectors", @@ -4976,7 +5002,6 @@ dependencies = [ name = "foundry-evm-fuzz" version = "0.0.7" dependencies = [ - "ahash", "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", @@ -4988,8 +5013,7 @@ dependencies = [ "foundry-evm-coverage", "foundry-evm-traces", "foundry-zksync-core", - "indexmap 2.7.0", - "itertools 0.13.0", + "itertools 0.14.0", "parking_lot", "proptest", "rand 0.8.5", @@ -5016,7 +5040,7 @@ dependencies = [ "foundry-evm-core", "foundry-linking", "futures 0.3.31", - "itertools 0.13.0", + "itertools 0.14.0", "rayon", "revm", "revm-inspectors", @@ -5094,7 +5118,7 @@ dependencies = [ "foundry-linking", "foundry-zksync-compilers", "foundry-zksync-core", - "itertools 0.13.0", + "itertools 0.14.0", "revm", "semver 1.0.23", "serde", @@ -5126,7 +5150,7 @@ dependencies = [ "snapbox", "tempfile", "tokio", - "tower-http 0.6.2", + "tower-http", "tracing", "tracing-subscriber", "zksync_types", @@ -5176,7 +5200,7 @@ dependencies = [ "foundry-compilers-artifacts-solc", "foundry-test-utils", "fs4 0.8.4", - "itertools 0.13.0", + "itertools 0.14.0", "path-slash", "reqwest 0.12.9", "semver 1.0.23", @@ -5207,7 +5231,7 @@ dependencies = [ "foundry-cheatcodes-common", "foundry-common", "foundry-evm-abi", - "itertools 0.13.0", + "itertools 0.14.0", "revm", "serde", "tracing", @@ -5958,6 +5982,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "html5ever" version = "0.27.0" @@ -6669,6 +6702,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -9200,6 +9242,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror 2.0.11", +] + [[package]] name = "regex" version = "1.11.1" @@ -9347,9 +9400,9 @@ dependencies = [ [[package]] name = "revm" -version = "19.3.0" +version = "19.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5a57589c308880c0f89ebf68d92aeef0d51e1ed88867474f895f6fd0f25c64" +checksum = "1538aea4d103a8044820eede9b1254e1b5a2a2abaf3f9a67bef19f8865cf1826" dependencies = [ "auto_impl", "cfg-if", @@ -10638,6 +10691,7 @@ checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa" dependencies = [ "anstream", "anstyle", + "anstyle-svg", "normalize-line-endings", "regex", "serde", @@ -11177,11 +11231,10 @@ dependencies = [ [[package]] name = "terminfo" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ - "dirs 4.0.0", "fnv", "nom", "phf", @@ -11606,9 +11659,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ "bitflags 2.6.0", "bytes", @@ -11629,20 +11682,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-http" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" -dependencies = [ - "bitflags 2.6.0", - "bytes", - "http 1.2.0", - "pin-project-lite", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.3" @@ -12032,6 +12071,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -12402,6 +12447,18 @@ dependencies = [ "winsafe", ] +[[package]] +name = "which" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "widestring" version = "1.1.0" @@ -12742,6 +12799,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 7b2b01af93..49ddd0c862 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ split-debuginfo = "unpacked" [profile.release] opt-level = 3 lto = "thin" -debug = "line-tables-only" +debug = "none" strip = "debuginfo" panic = "abort" codegen-units = 16 @@ -77,7 +77,7 @@ codegen-units = 16 # e.g. `cargo build --profile profiling` [profile.profiling] inherits = "release" -debug = 2 +debug = "full" split-debuginfo = "unpacked" strip = false @@ -179,14 +179,14 @@ foundry-zksync-inspectors = { path = "crates/zksync/inspectors" } foundry-strategy-zksync = { path = "crates/strategy/zksync" } # solc & compilation utilities -foundry-block-explorers = { version = "0.9.0", default-features = false } -foundry-compilers = { version = "0.12.9", default-features = false } +foundry-block-explorers = { version = "0.11.0", default-features = false } +foundry-compilers = { version = "0.13.0", default-features = false } foundry-fork-db = "0.10.0" solang-parser = "=0.3.3" solar-parse = { version = "=0.1.1", default-features = false } ## revm -revm = { version = "19.0.0", default-features = false } +revm = { version = "19.4.0", default-features = false } revm-primitives = { version = "15.1.0", default-features = false } revm-inspectors = { version = "0.14.1", features = ["serde"] } @@ -221,6 +221,7 @@ alloy-json-abi = "0.8.18" alloy-primitives = { version = "0.8.18", features = [ "getrandom", "rand", + "map-fxhash", "map-foldhash", ] } alloy-sol-macro-expander = "0.8.18" @@ -236,6 +237,8 @@ alloy-trie = "0.7.0" ## op-alloy op-alloy-rpc-types = "0.9.0" +## Note(zk): Foundry upstream change to maili-consensus but we need to keep +## it for zksync for now until era-solc updates the pinned version of serde op-alloy-consensus = "0.9.0" ## cli @@ -272,7 +275,6 @@ auto_impl = "1" bytes = "1.8" walkdir = "2" prettyplease = "0.2" -ahash = "0.8" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = [ "clock", @@ -281,6 +283,7 @@ chrono = { version = "0.4", default-features = false, features = [ axum = "0.7" color-eyre = "0.6" comfy-table = "7" +dirs = "6" dunce = "1" evm-disassembler = "0.5" evmole = "0.6" @@ -289,7 +292,7 @@ figment = "0.10" futures = "0.3" hyper = "1.5" indexmap = "2.6" -itertools = "0.13" +itertools = "0.14" jsonpath_lib = "0.3" k256 = "0.13" mesc = "0.3" @@ -310,8 +313,8 @@ tempfile = "3.13" tikv-jemallocator = "0.6" tokio = "1" toml = "0.8" -tower = "0.4" -tower-http = "0.5" +tower = "0.5" +tower-http = "0.6" tracing = "0.1" tracing-subscriber = "0.3" url = "2" diff --git a/Dockerfile.cross b/Dockerfile.cross new file mode 100644 index 0000000000..3efdde14ae --- /dev/null +++ b/Dockerfile.cross @@ -0,0 +1,28 @@ +# 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/LICENSE-APACHE b/LICENSE-APACHE index 3c4dfe081c..a7b8b5e6a6 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -184,4 +184,4 @@ comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier - identification within third-party archives. + identification within third-party archives. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT index 8dce0ba55e..38bfa70624 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile index 920e4de2d5..79544ce854 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,10 @@ # 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 @@ -26,6 +30,49 @@ help: ## Display this help. 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. + $(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 + + $(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 --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 + ##@ Other .PHONY: clean @@ -69,4 +116,4 @@ test: ## Run all tests. pr: ## Run all tests and linters in preparation for a PR. make lint && \ - make test \ No newline at end of file + make test diff --git a/README.md b/README.md index 032d724c32..4345bfb999 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,11 @@ See our [contributing guidelines](./CONTRIBUTING.md). ### Foundry -- Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/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) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. +- 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. ### Foundry ZKsync - [Moonsong Labs](https://moonsonglabs.com/): Implemented [ZKsync crates](./crates/zksync/), and resolved a number of different challenges to enable ZKsync support. diff --git a/clippy.toml b/clippy.toml index 8581063b6c..69dfa469ce 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,10 +1,11 @@ msrv = "1.83" -# bytes::Bytes is included by default and alloy_primitives::Bytes is a wrapper around it, -# so it is safe to ignore it as well + +# `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"] disallowed-macros = [ - # See `foundry_common::shell` + # See `foundry_common::shell`. { path = "std::print", reason = "use `sh_print` or similar macros instead" }, { path = "std::eprint", reason = "use `sh_eprint` or similar macros instead" }, { path = "std::println", reason = "use `sh_println` or similar macros instead" }, diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index d2f7ec576c..5948d8b838 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -18,13 +18,6 @@ name = "anvil" path = "src/anvil.rs" required-features = ["cli"] -[build-dependencies] -vergen = { workspace = true, default-features = false, features = [ - "build", - "git", - "gitcl", -] } - [dependencies] # foundry internal anvil-core = { path = "core", features = ["serde", "impersonated-tx"] } diff --git a/crates/anvil/README.md b/crates/anvil/README.md deleted file mode 100644 index efb0f9e2e5..0000000000 --- a/crates/anvil/README.md +++ /dev/null @@ -1,103 +0,0 @@ -## Anvil - -A local Ethereum node, akin to Ganache, designed for development with [**Forge**](../../bin/forge). - -## Features - -- Network forking: fork any EVM-compatible blockchain, same as in `forge` -- [Ethereum JSON-RPC](https://ethereum.org/developers/docs/apis/json-rpc/) support -- Additional JSON-RPC endpoints, compatible with ganache and hardhat - - snapshot/revert state - - mining modes: auto, interval, manual, none - - ... - -## Supported Versions - -- **anvil**: - - **evm**: Cancun -- **forge**: - - **solc**: Latest - - **evm**: Cancun - - -## Installation - -`anvil` binary is available via [`foundryup`](../../README.md#installation). - -### Installing from source - -```sh -cargo install --git https://github.com/foundry-rs/foundry anvil --locked --force -``` - -## Getting started - -```console -$ anvil - - _ _ - (_) | | - __ _ _ __ __ __ _ | | - / _` | | '_ \ \ \ / / | | | | - | (_| | | | | | \ V / | | | | - \__,_| |_| |_| \_/ |_| |_| - - 0.1.0 (8d507b4 2023-08-05T00:20:34.048397801Z) - https://github.com/foundry-rs/foundry - -Available Accounts -================== - -(0) "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" (10000.000000000000000000 ETH) -(1) "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (10000.000000000000000000 ETH) -(2) "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" (10000.000000000000000000 ETH) -(3) "0x90F79bf6EB2c4f870365E785982E1f101E93b906" (10000.000000000000000000 ETH) -(4) "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" (10000.000000000000000000 ETH) -(5) "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" (10000.000000000000000000 ETH) -(6) "0x976EA74026E726554dB657fA54763abd0C3a0aa9" (10000.000000000000000000 ETH) -(7) "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" (10000.000000000000000000 ETH) -(8) "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" (10000.000000000000000000 ETH) -(9) "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" (10000.000000000000000000 ETH) - -Private Keys -================== - -(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d -(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a -(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 -(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a -(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e -(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 -(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 -(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 - -Wallet -================== -Mnemonic: test test test test test test test test test test test junk -Derivation path: m/44'/60'/0'/0/ - - -Chain ID -================== - -31337 - -Base Fee -================== - -1000000000 - -Gas Limit -================== - -30000000 - -Genesis Timestamp -================== - -1692087429 - -Listening on 127.0.0.1:8545 -``` diff --git a/crates/anvil/build.rs b/crates/anvil/build.rs deleted file mode 100644 index c2f550fb6f..0000000000 --- a/crates/anvil/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index ffdcc17559..e8e1b9edd5 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -3,7 +3,8 @@ use anvil::cmd::NodeArgs; use clap::{CommandFactory, Parser, Subcommand}; use eyre::Result; -use foundry_cli::{opts::GlobalArgs, utils}; +use foundry_cli::{handler, opts::GlobalArgs, utils}; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; #[cfg(all(feature = "jemalloc", unix))] #[global_allocator] @@ -11,7 +12,7 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; /// A fast local Ethereum development node. #[derive(Parser)] -#[command(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] +#[command(name = "anvil", version = SHORT_VERSION, long_version = LONG_VERSION, next_display_order = None)] pub struct Anvil { /// Include the global arguments. #[command(flatten)] @@ -46,7 +47,9 @@ fn main() { } fn run() -> Result<()> { + handler::install(); utils::load_dotenv(); + utils::enable_paint(); let mut args = Anvil::parse(); args.global.init()?; @@ -90,6 +93,16 @@ mod tests { let _: Anvil = Anvil::parse_from(["anvil", "--help"]); } + #[test] + fn can_parse_short_version() { + let _: Anvil = Anvil::parse_from(["anvil", "-V"]); + } + + #[test] + fn can_parse_long_version() { + let _: Anvil = Anvil::parse_from(["anvil", "--version"]); + } + #[test] fn can_parse_completions() { let args: Anvil = Anvil::parse_from(["anvil", "completions", "bash"]); diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 8be6be750c..0bf102e186 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -587,9 +587,10 @@ pub struct AnvilEvmArgs { impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Some(fork_url) = &self.fork_url { - let 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) { + 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 }); + } } } } diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 247586bfdc..2286e37dec 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -57,6 +57,8 @@ use std::{ use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; +pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE; + /// Default port the rpc will open pub const NODE_PORT: u16 = 8545; /// Default chain id of the node @@ -69,15 +71,6 @@ pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test /// The default IPC endpoint pub const DEFAULT_IPC_ENDPOINT: &str = if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" }; -/// `anvil 0.1.0 (f01b232bc 2022-04-13T23:28:39.493201+00:00)` -pub const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); const BANNER: &str = r" _ _ @@ -1180,6 +1173,8 @@ latest block number: {latest_block}" }; let gas_limit = self.fork_gas_limit(&block); + self.gas_limit = Some(gas_limit); + env.block = BlockEnv { number: U256::from(fork_block_number), timestamp: U256::from(block.header.timestamp), diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 703c53b53a..0392a938c3 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -122,7 +122,7 @@ impl EthApi { pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result { node_info!("ots_hasCode"); let block_id = Some(BlockId::Number(block_number)); - Ok(self.get_code(address, block_id).await?.len() > 0) + Ok(!self.get_code(address, block_id).await?.is_empty()) } /// Trace a transaction and generate a trace call tree. diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 6d2e6d5e46..c9d2598e1d 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -1,4 +1,5 @@ -#![doc = include_str!("../README.md")] +//! Anvil is a fast local Ethereum development node. + #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use crate::{ diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 61adaa9335..e729240502 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1306,8 +1306,8 @@ async fn test_base_fork_gas_limit() { .unwrap() .unwrap(); - assert!(api.gas_limit() >= uint!(132_000_000_U256)); - assert!(block.header.gas_limit >= 132_000_000_u64); + assert!(api.gas_limit() >= uint!(96_000_000_U256)); + assert!(block.header.gas_limit >= 96_000_000_u64); } // diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index a4409746e1..c62ba1cf6b 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -20,13 +20,6 @@ name = "cast" name = "cast" path = "bin/main.rs" -[build-dependencies] -vergen = { workspace = true, default-features = false, features = [ - "build", - "git", - "gitcl", -] } - [dependencies] # lib foundry-block-explorers.workspace = true diff --git a/crates/cast/README.md b/crates/cast/README.md deleted file mode 100644 index 32de3fd74c..0000000000 --- a/crates/cast/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# `cast` - -Cast is a command-line tool for performing Ethereum RPC calls. You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line! - -For more information, see the [📖 Foundry Book (Cast Guide)](https://book.getfoundry.sh/cast/index.html). diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index 657fd02496..eeeb5d8531 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -10,23 +10,18 @@ use alloy_rpc_types::BlockId; use clap::{Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; -use foundry_common::ens::NameOrAddress; +use foundry_common::{ + ens::NameOrAddress, + version::{LONG_VERSION, SHORT_VERSION}, +}; use std::{path::PathBuf, str::FromStr}; -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); - -/// Perform Ethereum RPC calls from the comfort of your command line. +/// A Swiss Army knife for interacting with Ethereum applications from the command line. #[derive(Parser)] #[command( name = "cast", - version = VERSION_MESSAGE, + version = SHORT_VERSION, + long_version = LONG_VERSION, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", next_display_order = None, )] @@ -926,9 +921,9 @@ pub enum CastSubcommand { rpc: RpcOpts, }, - /// Get the source code of a contract from Etherscan. + /// Get the source code of a contract from a block explorer. #[command(visible_aliases = &["et", "src"])] - EtherscanSource { + Source { /// The contract's address. address: String, @@ -942,6 +937,15 @@ pub enum CastSubcommand { #[command(flatten)] etherscan: EtherscanOpts, + + /// Alternative explorer API URL to use that adheres to the Etherscan API. If not provided, + /// defaults to Etherscan. + #[arg(long, env = "EXPLORER_API_URL")] + explorer_api_url: Option, + + /// Alternative explorer browser URL. + #[arg(long, env = "EXPLORER_URL")] + explorer_url: Option, }, /// Wallet management utilities. diff --git a/crates/cast/bin/cmd/access_list.rs b/crates/cast/bin/cmd/access_list.rs index c283f60c40..4184912ffa 100644 --- a/crates/cast/bin/cmd/access_list.rs +++ b/crates/cast/bin/cmd/access_list.rs @@ -5,10 +5,9 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils, + utils::{self, LoadConfig}, }; use foundry_common::ens::NameOrAddress; -use foundry_config::Config; use std::str::FromStr; /// CLI arguments for `cast access-list`. @@ -46,7 +45,7 @@ impl AccessListArgs { pub async fn run(self) -> Result<()> { let Self { to, sig, args, tx, eth, block } = self; - let config = Config::from(ð); + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; let sender = SenderKind::from_wallet_opts(eth.wallet).await?; diff --git a/crates/cast/bin/cmd/artifact.rs b/crates/cast/bin/cmd/artifact.rs index 2e7a7cae01..c95bd4d898 100644 --- a/crates/cast/bin/cmd/artifact.rs +++ b/crates/cast/bin/cmd/artifact.rs @@ -5,10 +5,9 @@ use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils, + utils::{self, LoadConfig}, }; use foundry_common::fs; -use foundry_config::Config; use serde_json::json; use std::path::PathBuf; @@ -51,7 +50,7 @@ impl ArtifactArgs { let Self { contract, etherscan, rpc, output: output_location, abi_path } = self; let mut etherscan = etherscan; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; diff --git a/crates/cast/bin/cmd/bind.rs b/crates/cast/bin/cmd/bind.rs index 942c441f6c..159302344e 100644 --- a/crates/cast/bin/cmd/bind.rs +++ b/crates/cast/bin/cmd/bind.rs @@ -57,7 +57,7 @@ impl BindArgs { pub async fn run(self) -> Result<()> { Err(eyre::eyre!( "`cast bind` has been removed.\n\ - Please use `cast etherscan-source` to create a Forge project from an Etherscan source\n\ + Please use `cast source` to create a Forge project from a block explorer source\n\ and `forge bind` to generate the bindings to it instead." )) } diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 54c80728b7..df90483eb8 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -119,7 +119,7 @@ impl CallArgs { pub async fn run(self) -> Result<()> { let figment = Into::::into(&self.eth).merge(&self); let evm_opts = figment.extract::()?; - let mut config = Config::try_from(figment)?.sanitized(); + let mut config = Config::from_provider(figment)?.sanitized(); let strategy = utils::get_executor_strategy(&config); let Self { diff --git a/crates/cast/bin/cmd/constructor_args.rs b/crates/cast/bin/cmd/constructor_args.rs index 8fe42c93cf..2775e2e99e 100644 --- a/crates/cast/bin/cmd/constructor_args.rs +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -1,3 +1,7 @@ +use super::{ + creation_code::fetch_creation_code, + interface::{fetch_abi_from_etherscan, load_abi_from_file}, +}; use alloy_dyn_abi::DynSolType; use alloy_primitives::{Address, Bytes}; use alloy_provider::Provider; @@ -6,13 +10,7 @@ use eyre::{eyre, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils, -}; -use foundry_config::Config; - -use super::{ - creation_code::fetch_creation_code, - interface::{fetch_abi_from_etherscan, load_abi_from_file}, + utils::{self, LoadConfig}, }; /// CLI arguments for `cast creation-args`. @@ -35,10 +33,9 @@ pub struct ConstructorArgsArgs { impl ConstructorArgsArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc, abi_path } = self; + let Self { contract, mut etherscan, rpc, abi_path } = self; - let mut etherscan = etherscan; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index b444bff320..727e5f0d67 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -1,3 +1,4 @@ +use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; use alloy_consensus::Transaction; use alloy_primitives::{Address, Bytes}; use alloy_provider::{ext::TraceApi, Provider}; @@ -8,12 +9,9 @@ use eyre::{eyre, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils, + utils::{self, LoadConfig}, }; use foundry_common::provider::RetryProvider; -use foundry_config::Config; - -use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; /// CLI arguments for `cast creation-code`. #[derive(Parser)] @@ -47,11 +45,10 @@ pub struct CreationCodeArgs { impl CreationCodeArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc, disassemble, without_args, only_args, abi_path } = + let Self { contract, mut etherscan, rpc, disassemble, without_args, only_args, abi_path } = self; - let mut etherscan = etherscan; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs index 3454db1972..af08cd220a 100644 --- a/crates/cast/bin/cmd/estimate.rs +++ b/crates/cast/bin/cmd/estimate.rs @@ -6,10 +6,9 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, parse_ether_value}, + utils::{self, parse_ether_value, LoadConfig}, }; use foundry_common::ens::NameOrAddress; -use foundry_config::Config; use std::str::FromStr; /// CLI arguments for `cast estimate`. @@ -69,7 +68,7 @@ impl EstimateArgs { pub async fn run(self) -> Result<()> { let Self { to, mut sig, mut args, mut tx, block, eth, command } = self; - let config = Config::from(ð); + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; let sender = SenderKind::from_wallet_opts(eth.wallet).await?; diff --git a/crates/cast/bin/cmd/find_block.rs b/crates/cast/bin/cmd/find_block.rs index dc7750490b..b77845e189 100644 --- a/crates/cast/bin/cmd/find_block.rs +++ b/crates/cast/bin/cmd/find_block.rs @@ -2,8 +2,10 @@ use alloy_provider::Provider; use cast::Cast; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::RpcOpts, utils}; -use foundry_config::Config; +use foundry_cli::{ + opts::RpcOpts, + utils::{self, LoadConfig}, +}; use futures::join; /// CLI arguments for `cast find-block`. @@ -21,7 +23,7 @@ impl FindBlockArgs { let Self { timestamp, rpc } = self; let ts_target = timestamp; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let last_block_num = provider.get_block_number().await?; diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index 659ce6ccaa..28ce1ed31e 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -3,10 +3,10 @@ use alloy_primitives::Address; use clap::Parser; use eyre::{Context, Result}; use foundry_block_explorers::Client; -use foundry_cli::opts::EtherscanOpts; +use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; use foundry_common::{compile::ProjectCompiler, fs, shell}; use foundry_compilers::{info::ContractInfo, utils::canonicalize}; -use foundry_config::{load_config_with_root, try_find_project_root, Config}; +use foundry_config::load_config; use itertools::Itertools; use serde_json::Value; use std::{ @@ -114,8 +114,7 @@ pub fn load_abi_from_file(path: &str, name: Option) -> Result Result> { - let root = try_find_project_root(None)?; - let config = load_config_with_root(Some(&root)); + let config = load_config()?; let project = config.project()?; let compiler = ProjectCompiler::new().quiet(true); @@ -139,7 +138,7 @@ pub async fn fetch_abi_from_etherscan( address: Address, etherscan: &EtherscanOpts, ) -> Result> { - let config = Config::from(etherscan); + let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index 154b5c9d21..f6a756f69e 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -6,9 +6,8 @@ use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, Filt use cast::Cast; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::EthereumOpts, utils}; +use foundry_cli::{opts::EthereumOpts, utils, utils::LoadConfig}; use foundry_common::ens::NameOrAddress; -use foundry_config::Config; use itertools::Itertools; use std::{io, str::FromStr}; @@ -58,7 +57,7 @@ impl LogsArgs { let Self { from_block, to_block, address, sig_or_topic, topics_or_args, subscribe, eth } = self; - let config = Config::from(ð); + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs index 7837aaed0d..085548d52d 100644 --- a/crates/cast/bin/cmd/mktx.rs +++ b/crates/cast/bin/cmd/mktx.rs @@ -6,10 +6,9 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::get_provider, + utils::{get_provider, LoadConfig}, }; use foundry_common::ens::NameOrAddress; -use foundry_config::Config; use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast mktx`. @@ -82,7 +81,7 @@ impl MakeTxArgs { None }; - let config = Config::from(ð); + let config = eth.load_config()?; // Retrieve the signer, and bail if it can't be constructed. let signer = eth.wallet.signer().await?; diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index 3f57b76689..223d133f12 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -1,4 +1,4 @@ -//! Subcommands for cast +//! `cast` subcommands. //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should diff --git a/crates/cast/bin/cmd/rpc.rs b/crates/cast/bin/cmd/rpc.rs index d901c760be..fa3facfd08 100644 --- a/crates/cast/bin/cmd/rpc.rs +++ b/crates/cast/bin/cmd/rpc.rs @@ -1,8 +1,7 @@ use cast::Cast; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::RpcOpts, utils}; -use foundry_config::Config; +use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use itertools::Itertools; /// CLI arguments for `cast rpc`. @@ -37,7 +36,7 @@ impl RpcArgs { pub async fn run(self) -> Result<()> { let Self { raw, method, params, rpc } = self; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let params = if raw { diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index d4ad142793..e17da50929 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -103,7 +103,7 @@ impl RunArgs { pub async fn run(self) -> Result<()> { let figment = Into::::into(&self.rpc).merge(&self); let evm_opts = figment.extract::()?; - let mut config = Config::try_from(figment)?.sanitized(); + let mut config = Config::from_provider(figment)?.sanitized(); let strategy = utils::get_executor_strategy(&config); let compute_units_per_second = diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 4af89835bb..b62215079d 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -20,9 +20,9 @@ use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, + utils::LoadConfig, }; use foundry_common::ens::NameOrAddress; -use foundry_config::Config; use std::{path::PathBuf, str::FromStr, sync::Arc}; /// ZkSync-specific paymaster parameters for transactions @@ -144,7 +144,7 @@ impl SendTxArgs { None }; - let mut config = Config::from(ð); + let mut config = eth.load_config()?; config.zksync.startup = zksync_params.zksync; config.zksync.compile = zksync_params.zksync; diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 4499fea1cf..2c445712ab 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -12,6 +12,7 @@ use foundry_block_explorers::Client; use foundry_cli::{ opts::{BuildOpts, EtherscanOpts, RpcOpts}, utils, + utils::LoadConfig, }; use foundry_common::{ abi::find_source, @@ -85,7 +86,7 @@ impl figment::Provider for StorageArgs { impl StorageArgs { pub async fn run(self) -> Result<()> { - let config = Config::from(&self); + let config = self.load_config()?; let Self { address, slot, block, build, .. } = self; let provider = utils::get_provider(&config)?; @@ -350,11 +351,11 @@ mod tests { #[test] fn parse_storage_etherscan_api_key() { let args = - StorageArgs::parse_from(["foundry-cli", "addr", "--etherscan-api-key", "dummykey"]); + 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"); - let config = Config::from(&args); + let config = args.load_config().unwrap(); std::env::remove_var("ETHERSCAN_API_KEY"); assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 4234304f25..9d83c7e21a 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -10,7 +10,7 @@ use alloy_signer_local::{ use cast::revm::primitives::Authorization; use clap::Parser; use eyre::{Context, Result}; -use foundry_cli::{opts::RpcOpts, utils}; +use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use foundry_common::{fs, sh_println, shell}; use foundry_config::Config; use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; @@ -369,7 +369,7 @@ impl WalletSubcommands { } Self::SignAuth { rpc, nonce, chain, wallet, address } => { let wallet = wallet.signer().await?; - let provider = utils::get_provider(&Config::from(&rpc))?; + let provider = utils::get_provider(&rpc.load_config()?)?; let nonce = if let Some(nonce) = nonce { nonce } else { diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 749c31d5c5..9b9ce95f26 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -9,7 +9,7 @@ use cast::{Cast, SimpleCast}; use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; -use foundry_cli::{handler, utils}; +use foundry_cli::{handler, utils, utils::LoadConfig}; use foundry_common::{ abi::{get_error, get_event}, ens::{namehash, ProviderEnsExt}, @@ -292,7 +292,7 @@ async fn main_args(args: CastArgs) -> Result<()> { // Blockchain & RPC queries CastSubcommand::AccessList(cmd) => cmd.run().await?, CastSubcommand::Age { block, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", @@ -300,7 +300,7 @@ async fn main_args(args: CastArgs) -> Result<()> { )? } CastSubcommand::Balance { block, who, ether, rpc, erc20 } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let account_addr = who.resolve(&provider).await?; @@ -321,7 +321,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } } CastSubcommand::BaseFee { block, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", @@ -329,7 +329,7 @@ async fn main_args(args: CastArgs) -> Result<()> { )? } CastSubcommand::Block { block, full, field, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", @@ -339,7 +339,7 @@ async fn main_args(args: CastArgs) -> Result<()> { )? } CastSubcommand::BlockNumber { rpc, block } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let number = match block { Some(id) => { @@ -355,34 +355,34 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{number}")? } CastSubcommand::Chain { rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).chain().await?)? } CastSubcommand::ChainId { rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).chain_id().await?)? } CastSubcommand::Client { rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", provider.get_client_version().await?)? } CastSubcommand::Code { block, who, disassemble, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).code(who, block, disassemble).await?)? } CastSubcommand::Codesize { block, who, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codesize(who, block).await?)? } CastSubcommand::ComputeAddress { address, nonce, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let address = stdin::unwrap_line(address)?; @@ -418,7 +418,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } CastSubcommand::FindBlock(cmd) => cmd.run().await?, CastSubcommand::GasPrice { rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).gas_price().await?)?; } @@ -431,37 +431,37 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{}", foundry_common::erc7201(&id))?; } CastSubcommand::Implementation { block, beacon, who, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).implementation(who, beacon, block).await?)?; } CastSubcommand::Admin { block, who, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).admin(who, block).await?)?; } CastSubcommand::Nonce { block, who, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).nonce(who, block).await?)?; } CastSubcommand::Codehash { block, who, slots, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codehash(who, slots, block).await?)?; } CastSubcommand::StorageRoot { block, who, slots, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).storage_root(who, slots, block).await?)?; } CastSubcommand::Proof { address, slots, rpc, block } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; let value = provider @@ -478,7 +478,7 @@ async fn main_args(args: CastArgs) -> Result<()> { CastSubcommand::Estimate(cmd) => cmd.run().await?, CastSubcommand::MakeTx(cmd) => cmd.run().await?, CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let pending_tx = cast.publish(raw_tx).await?; @@ -492,7 +492,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } } CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", @@ -504,7 +504,7 @@ async fn main_args(args: CastArgs) -> Result<()> { CastSubcommand::Run(cmd) => cmd.run().await?, CastSubcommand::SendTx(cmd) => cmd.run().await?, CastSubcommand::Tx { tx_hash, field, raw, rpc } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; // Can use either --raw or specify raw as a field @@ -570,7 +570,7 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{}", namehash(&name))? } CastSubcommand::LookupAddress { who, rpc, verify } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; @@ -585,7 +585,7 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{name}")? } CastSubcommand::ResolveName { who, rpc, verify } => { - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; @@ -635,20 +635,50 @@ async fn main_args(args: CastArgs) -> Result<()> { "{}", SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)? )?, - CastSubcommand::EtherscanSource { address, directory, etherscan, flatten } => { - let config = Config::from(ðerscan); + CastSubcommand::Source { + address, + directory, + explorer_api_url, + explorer_url, + etherscan, + flatten, + } => { + let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let api_key = config.get_etherscan_api_key(Some(chain)); match (directory, flatten) { (Some(dir), false) => { - SimpleCast::expand_etherscan_source_to_directory(chain, address, api_key, dir) - .await? - } - (None, false) => { - sh_println!("{}", SimpleCast::etherscan_source(chain, address, api_key).await?)? + SimpleCast::expand_etherscan_source_to_directory( + chain, + address, + api_key, + dir, + explorer_api_url, + explorer_url, + ) + .await? } + (None, false) => sh_println!( + "{}", + SimpleCast::etherscan_source( + chain, + address, + api_key, + explorer_api_url, + explorer_url + ) + .await? + )?, (dir, true) => { - SimpleCast::etherscan_source_flatten(chain, address, api_key, dir).await?; + SimpleCast::etherscan_source_flatten( + chain, + address, + api_key, + dir, + explorer_api_url, + explorer_url, + ) + .await?; } } } diff --git a/crates/cast/build.rs b/crates/cast/build.rs deleted file mode 100644 index c2f550fb6f..0000000000 --- a/crates/cast/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index b2629ccbcb..20ae80245c 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,4 +1,5 @@ -#![doc = include_str!("../README.md")] +//! Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. + #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use alloy_consensus::TxEnvelope; @@ -21,7 +22,7 @@ use alloy_sol_types::sol; use alloy_transport::Transport; use base::{Base, NumberWithBase, ToBase}; use chrono::DateTime; -use eyre::{Context, ContextCompat, Result}; +use eyre::{Context, ContextCompat, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_common::{ abi::{encode_function_args, get_func}, @@ -457,6 +458,8 @@ where _ => "avalanche", } } + "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", + "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", _ => "unknown", }) } @@ -1938,7 +1941,9 @@ impl SimpleCast { /// Cast::etherscan_source( /// NamedChain::Mainnet.into(), /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), - /// "".to_string() + /// Some("".to_string()), + /// None, + /// None /// ) /// .await /// .unwrap() @@ -1950,9 +1955,11 @@ impl SimpleCast { pub async fn etherscan_source( chain: Chain, contract_address: String, - etherscan_api_key: String, + etherscan_api_key: Option, + explorer_api_url: Option, + explorer_url: Option, ) -> Result { - let client = Client::new(chain, etherscan_api_key)?; + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let metadata = client.contract_source_code(contract_address.parse()?).await?; Ok(metadata.source_code()) } @@ -1970,8 +1977,10 @@ impl SimpleCast { /// Cast::expand_etherscan_source_to_directory( /// NamedChain::Mainnet.into(), /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), - /// "".to_string(), + /// Some("".to_string()), /// PathBuf::from("output_dir"), + /// None, + /// None, /// ) /// .await?; /// # Ok(()) @@ -1980,10 +1989,12 @@ impl SimpleCast { pub async fn expand_etherscan_source_to_directory( chain: Chain, contract_address: String, - etherscan_api_key: String, + etherscan_api_key: Option, output_directory: PathBuf, + explorer_api_url: Option, + explorer_url: Option, ) -> eyre::Result<()> { - let client = Client::new(chain, etherscan_api_key)?; + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let meta = client.contract_source_code(contract_address.parse()?).await?; let source_tree = meta.source_tree(); source_tree.write_to(&output_directory)?; @@ -1995,10 +2006,12 @@ impl SimpleCast { pub async fn etherscan_source_flatten( chain: Chain, contract_address: String, - etherscan_api_key: String, + etherscan_api_key: Option, output_path: Option, + explorer_api_url: Option, + explorer_url: Option, ) -> Result<()> { - let client = Client::new(chain, etherscan_api_key)?; + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let metadata = client.contract_source_code(contract_address.parse()?).await?; let Some(metadata) = metadata.items.first() else { eyre::bail!("Empty contract source code") @@ -2192,6 +2205,33 @@ fn strip_0x(s: &str) -> &str { s.strip_prefix("0x").unwrap_or(s) } +fn explorer_client( + chain: Chain, + api_key: Option, + api_url: Option, + explorer_url: Option, +) -> Result { + let mut builder = Client::builder().with_chain_id(chain); + + let deduced = chain.etherscan_urls(); + + let explorer_url = explorer_url + .or(deduced.map(|d| d.1.to_string())) + .ok_or_eyre("Please provide the explorer browser URL using `--explorer-url`")?; + builder = builder.with_url(explorer_url)?; + + let api_url = api_url + .or(deduced.map(|d| d.0.to_string())) + .ok_or_eyre("Please provide the explorer API URL using `--explorer-api-url`")?; + builder = builder.with_api_url(api_url)?; + + if let Some(api_key) = api_key { + builder = builder.with_api_key(api_key); + } + + builder.build().map_err(Into::into) +} + #[cfg(test)] mod tests { use super::SimpleCast as Cast; diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 1a05c7e097..8b57c8d5ed 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -18,10 +18,27 @@ use foundry_test_utils::{ use std::{fs, io::Write, path::Path, str::FromStr}; mod zk; +casttest!(print_short_version, |_prj, cmd| { + cmd.arg("-V").assert_success().stdout_eq(str![[r#" +cast [..]-[..] ([..] [..]) + +"#]]); +}); + +casttest!(print_long_version, |_prj, cmd| { + cmd.arg("--version").assert_success().stdout_eq(str![[r#" +cast Version: [..] +Commit SHA: [..] +Build Timestamp: [..] +Build Profile: [..] + +"#]]); +}); + // tests `--help` is printed to std out casttest!(print_help, |_prj, cmd| { cmd.arg("--help").assert_success().stdout_eq(str![[r#" -Perform Ethereum RPC calls from the comfort of your command line +A Swiss Army knife for interacting with Ethereum applications from the command line Usage: cast[..] @@ -1833,7 +1850,7 @@ Nothing to compile .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: - [13520] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 + [..] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 ├─ emit topic 0: 0xa7263295d3a687d750d1fd377b5df47de69d7db8decc745aaa4bbee44dc1688d │ data: 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 └─ ← [Return] 62 bytes of code @@ -1853,7 +1870,7 @@ Executing previous transactions from the block. Compiling project to generate artifacts No files changed, compilation skipped Traces: - [13520] → new LocalProjectContract@0x5FbDB2315678afecb367f032d93F642f64180aa3 + [..] → new LocalProjectContract@0x5FbDB2315678afecb367f032d93F642f64180aa3 ├─ emit LocalProjectContractCreated(owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266) └─ ← [Return] 62 bytes of code @@ -1915,7 +1932,7 @@ forgetest_async!(show_state_changes_in_traces, |prj, cmd| { .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: - [22287] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) + [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) ├─ storage changes: │ @ 0: 0 → 111 └─ ← [Stop] @@ -2006,8 +2023,8 @@ contract CounterInExternalLibScript is Script { .stdout_eq(str![[r#" ... Traces: - [37739] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - ├─ [22411] 0xfAb06527117d29EA121998AC4fAB9Fc88bF5f979::updateCounterInExternalLib(0, 100) [delegatecall] + [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ├─ [..] 0x52F3e85EC3F0f9D0a2200D646482fcD134D5adc9::updateCounterInExternalLib(0, 100) [delegatecall] │ └─ ← [Stop] └─ ← [Return] 62 bytes of code @@ -2070,3 +2087,43 @@ forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { .args(["run", &receipt.transaction_hash.to_string(), "--rpc-url", &http_endpoint]) .assert_success(); }); + +// +casttest!(fetch_src_blockscout, |_prj, cmd| { + let url = "https://eth.blockscout.com/api"; + + let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + + 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!(fetch_src_default, |_prj, cmd| { + let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + let etherscan_api_key = next_mainnet_etherscan_api_key(); + + cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", ðerscan_api_key]) + .assert_success() + .stdout_eq(str![[r#" +... +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; +..."#]]); +}); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 56283b2122..3f33936790 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -14,13 +14,6 @@ exclude.workspace = true [lints] workspace = true -[build-dependencies] -vergen = { workspace = true, default-features = false, features = [ - "build", - "git", - "gitcl", -] } - [dependencies] foundry-cheatcodes-spec.workspace = true foundry-cheatcodes-common.workspace = true @@ -52,7 +45,6 @@ alloy-network.workspace = true alloy-rlp.workspace = true base64.workspace = true -chrono.workspace = true dialoguer = "0.11" eyre.workspace = true itertools.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 288caa6a56..c7e58cae96 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -604,6 +604,27 @@ "description": "Address of the contract implementation that will be delegated to.\n Gets encoded into delegation code: 0xef0100 || implementation." } ] + }, + { + "name": "PotentialRevert", + "description": "Represents a \"potential\" revert reason from a single subsequent call when using `vm.assumeNoReverts`.\n Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced\n as normal.", + "fields": [ + { + "name": "reverter", + "ty": "address", + "description": "The allowed origin of the revert opcode; address(0) allows reverts from any address" + }, + { + "name": "partialMatch", + "ty": "bool", + "description": "When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data" + }, + { + "name": "revertData", + "ty": "bytes", + "description": "The data to use to match encountered reverts" + } + ] } ], "cheatcodes": [ @@ -3089,7 +3110,7 @@ }, { "func": { - "id": "assumeNoRevert", + "id": "assumeNoRevert_0", "description": "Discard this run's fuzz inputs and generate new ones if next call reverted.", "declaration": "function assumeNoRevert() external pure;", "visibility": "external", @@ -3107,6 +3128,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "assumeNoRevert_1", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters.", + "declaration": "function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert((address,bool,bytes))", + "selector": "0xd8591eeb", + "selectorBytes": [ + 216, + 89, + 30, + 235 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assumeNoRevert_2", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters.", + "declaration": "function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert((address,bool,bytes)[])", + "selector": "0x8a4592cc", + "selectorBytes": [ + 138, + 69, + 146, + 204 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "attachDelegation", @@ -5714,7 +5775,7 @@ { "func": { "id": "getFoundryVersion", - "description": "Returns the Foundry version.\nFormat: ++\nSample output: 0.2.0+faa94c384+202407110019\nNote: Build timestamps may vary slightly across platforms due to separate CI jobs.\nFor reliable version comparisons, use YYYYMMDD0000 format (e.g., >= 202407110000)\nto compare timestamps while ignoring minor time differences.", + "description": "Returns the Foundry version.\nFormat: -+..\nSample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug\nNote: Build timestamps may vary slightly across platforms due to separate CI jobs.\nFor reliable version comparisons, use UNIX format (e.g., >= 1700000000)\nto compare timestamps while ignoring minor time differences.", "declaration": "function getFoundryVersion() external view returns (string memory version);", "visibility": "external", "mutability": "view", diff --git a/crates/cheatcodes/build.rs b/crates/cheatcodes/build.rs deleted file mode 100644 index c2f550fb6f..0000000000 --- a/crates/cheatcodes/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/cheatcodes/spec/README.md b/crates/cheatcodes/spec/README.md deleted file mode 100644 index 0b0aecebed..0000000000 --- a/crates/cheatcodes/spec/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# foundry-cheatcodes-spec - -Minimal crate to provide a cheatcodes specification. diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index c4d7e9868f..6dd6ee769f 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -1,4 +1,5 @@ -#![doc = include_str!("../README.md")] +//! Cheatcode specification for Foundry. + #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -88,6 +89,7 @@ impl Cheatcodes<'static> { Vm::DebugStep::STRUCT.clone(), Vm::BroadcastTxSummary::STRUCT.clone(), Vm::SignedDelegation::STRUCT.clone(), + Vm::PotentialRevert::STRUCT.clone(), ]), enums: Cow::Owned(vec![ Vm::CallerMode::ENUM.clone(), diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 67cddcb5b8..12891c6ad5 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -324,6 +324,18 @@ interface Vm { address implementation; } + /// Represents a "potential" revert reason from a single subsequent call when using `vm.assumeNoReverts`. + /// Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced + /// as normal. + struct PotentialRevert { + /// The allowed origin of the revert opcode; address(0) allows reverts from any address + address reverter; + /// When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data + bool partialMatch; + /// The data to use to match encountered reverts + bytes revertData; + } + // ======== EVM ======== /// Gets the address for a given private key. @@ -924,6 +936,14 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function assumeNoRevert() external pure; + /// Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; + + /// Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; + /// Writes a breakpoint to jump to in the debugger. #[cheatcode(group = Testing, safety = Safe)] function breakpoint(string calldata char) external pure; @@ -933,10 +953,10 @@ interface Vm { function breakpoint(string calldata char, bool value) external pure; /// Returns the Foundry version. - /// Format: ++ - /// Sample output: 0.2.0+faa94c384+202407110019 + /// Format: -+.. + /// Sample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug /// Note: Build timestamps may vary slightly across platforms due to separate CI jobs. - /// For reliable version comparisons, use YYYYMMDD0000 format (e.g., >= 202407110000) + /// For reliable version comparisons, use UNIX format (e.g., >= 1700000000) /// to compare timestamps while ignoring minor time differences. #[cheatcode(group = Testing, safety = Safe)] function getFoundryVersion() external view returns (string memory version); diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 90d49f3e6c..1c1d2ef308 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -2,13 +2,12 @@ use super::Result; use crate::{strategy::CheatcodeInspectorStrategy, Vm::Rpc}; use alloy_primitives::{map::AddressHashMap, U256}; use foundry_common::{fs::normalize_path, ContractsByArtifact}; -use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; +use foundry_compilers::{utils::canonicalize, ArtifactId, ProjectPathsConfig}; use foundry_config::{ cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions, ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, }; use foundry_evm_core::opts::EvmOpts; -use semver::Version; use std::{ path::{Path, PathBuf}, time::Duration, @@ -49,16 +48,16 @@ pub struct CheatsConfig { /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. /// If None, no validation is performed. pub available_artifacts: Option, - /// Name of the script/test contract which is currently running. - pub running_contract: Option, - /// Version of the script/test contract which is currently running. - pub running_version: Option, + /// Currently running artifact. + pub running_artifact: Option, /// The behavior strategy. pub strategy: CheatcodeInspectorStrategy, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, /// Optional seed for the RNG algorithm. pub seed: Option, + /// Whether to allow `expectRevert` to work for internal calls. + pub internal_expect_revert: bool, } impl CheatsConfig { @@ -68,8 +67,7 @@ impl CheatsConfig { config: &Config, evm_opts: EvmOpts, available_artifacts: Option, - running_contract: Option, - running_version: Option, + running_artifact: Option, strategy: CheatcodeInspectorStrategy, ) -> Self { let mut allowed_paths = vec![config.root.clone()]; @@ -98,11 +96,11 @@ impl CheatsConfig { evm_opts, labels: config.labels.clone(), available_artifacts, - running_contract, - running_version, + running_artifact, strategy, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, + internal_expect_revert: config.allow_internal_expect_revert, } } @@ -112,8 +110,7 @@ impl CheatsConfig { config, evm_opts, self.available_artifacts.clone(), - self.running_contract.clone(), - self.running_version.clone(), + self.running_artifact.clone(), self.strategy.clone(), ) } @@ -236,11 +233,11 @@ impl Default for CheatsConfig { evm_opts: Default::default(), labels: Default::default(), available_artifacts: Default::default(), - running_contract: Default::default(), - running_version: Default::default(), + running_artifact: Default::default(), strategy: CheatcodeInspectorStrategy::new_evm(), assertions_revert: true, seed: None, + internal_expect_revert: false, } } } @@ -256,7 +253,6 @@ mod tests { Default::default(), None, None, - None, CheatcodeInspectorStrategy::new_evm(), ) } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index c2c220edfe..b77c889d59 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -283,6 +283,7 @@ impl_from!( alloy_sol_types::Error, alloy_dyn_abi::Error, alloy_primitives::SignatureError, + eyre::Report, FsPathError, hex::FromHexError, BackendError, @@ -306,12 +307,6 @@ impl> From> for Error { } } -impl From for Error { - fn from(err: eyre::Report) -> Self { - Self::from(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 46da5d0b68..1822dac9a6 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -12,9 +12,8 @@ use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::fs::{read_json_file, write_json_file}; use foundry_evm_core::{ - abi::HARDHAT_CONSOLE_ADDRESS, backend::{DatabaseExt, RevertStateSnapshotAction}, - constants::{CALLER, CHEATCODE_ADDRESS, TEST_CONTRACT_ADDRESS}, + constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, }; use foundry_evm_traces::StackSnapshotType; use rand::Rng; @@ -1032,7 +1031,7 @@ fn derive_snapshot_name( name: Option, ) -> (String, String) { let group = group.unwrap_or_else(|| { - ccx.state.config.running_contract.clone().expect("expected running contract") + ccx.state.config.running_artifact.clone().expect("expected running contract").name }); let name = name.unwrap_or_else(|| "default".to_string()); (group, name) @@ -1140,14 +1139,15 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap BTreeMap { diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 15475612fe..83fe3b73d5 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -413,20 +413,31 @@ pub fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Resu let artifact = match &filtered[..] { [] => Err(fmt_err!("no matching artifact found")), - [artifact] => Ok(artifact), + [artifact] => Ok(*artifact), filtered => { - // If we know the current script/test contract solc version, try to filter - // by it + let mut filtered = filtered.to_vec(); + // If we know the current script/test contract solc version, try to filter by it state .config - .running_version + .running_artifact .as_ref() - .and_then(|version| { - let filtered = filtered - .iter() - .filter(|(id, _)| id.version == *version) - .collect::>(); - (filtered.len() == 1).then(|| filtered[0]) + .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")) } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6388016e6e..3d1ce3e584 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -11,6 +11,7 @@ use crate::{ test::{ assume::AssumeNoRevert, expect::{self, ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind}, + revert_handlers, }, utils::IgnoredTraces, CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, @@ -29,9 +30,9 @@ use foundry_cheatcodes_common::{ }; use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_evm_core::{ - abi::{Vm::stopExpectSafeMemoryCall, HARDHAT_CONSOLE_ADDRESS}, + abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, - constants::{CHEATCODE_ADDRESS, MAGIC_ASSUME}, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, utils::new_evm_with_existing_context, InspectorExt, }; @@ -56,6 +57,7 @@ use revm::{ }; use serde_json::Value; use std::{ + cmp::max, collections::{BTreeMap, VecDeque}, fs::File, io::BufReader, @@ -125,8 +127,8 @@ pub trait CheatcodesExecutor { }) } - fn console_log(&mut self, ccx: &mut CheatsCtxt, message: String) { - self.get_inspector(ccx.state).console_log(message); + fn console_log(&mut self, ccx: &mut CheatsCtxt, msg: &str) { + self.get_inspector(ccx.state).console_log(msg); } /// Returns a mutable reference to the tracing inspector if it is available. @@ -843,16 +845,15 @@ where { matches!(expected_revert.kind, ExpectedRevertKind::Default) { let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - let handler_result = expect::handle_expect_revert( + return match revert_handlers::handle_expect_revert( false, true, - &mut expected_revert, + self.config.internal_expect_revert, + &expected_revert, outcome.result.result, outcome.result.output.clone(), &self.config.available_artifacts, - ); - - return match handler_result { + ) { Ok((address, retdata)) => { expected_revert.actual_count += 1; if expected_revert.actual_count < expected_revert.count { @@ -1281,6 +1282,11 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { 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); + } + self.strategy.runner.post_initialize_interp( self.strategy.context.as_mut(), interpreter, @@ -1401,36 +1407,61 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { } } - // Handle assume not revert cheatcode. - if let Some(assume_no_revert) = &self.assume_no_revert { - if ecx.journaled_state.depth() == assume_no_revert.depth && !cheatcode_call { - // Discard run if we're at the same depth as cheatcode and call reverted. + // Handle assume no revert cheatcode. + if let Some(assume_no_revert) = &mut self.assume_no_revert { + // Record current reverter address before processing the expect revert if call reverted, + // expect revert is set with expected reverter address and no actual reverter set yet. + if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() { + assume_no_revert.reverted_by = Some(call.target_address); + } + // allow multiple cheatcode calls at the same depth + if ecx.journaled_state.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 if outcome.result.is_revert() { - outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); + let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap(); + return match revert_handlers::handle_assume_no_revert( + &assume_no_revert, + outcome.result.result, + &outcome.result.output, + &self.config.available_artifacts, + ) { + // if result is Ok, it was an anticipated revert; return an "assume" error + // to reject this run + Ok(_) => { + outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); + outcome + } + // if result is Error, it was an unanticipated revert; should revert + // normally + Err(error) => { + trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = error.abi_encode().into(); + outcome + } + } + } else { + // Call didn't revert, reset `assume_no_revert` state. + self.assume_no_revert = None; return outcome; } - // Call didn't revert, reset `assume_no_revert` state. - self.assume_no_revert = None; } } // Handle expected reverts. if let Some(expected_revert) = &mut self.expected_revert { - // Record current reverter address before processing the expect revert if call reverted, - // expect revert is set with expected reverter address and no actual reverter set yet. - if outcome.result.is_revert() && - expected_revert.reverter.is_some() && - expected_revert.reverted_by.is_none() - { - expected_revert.reverted_by = Some(call.target_address); - } else if outcome.result.is_revert() && - expected_revert.reverter.is_some() && - expected_revert.reverted_by.is_some() && - expected_revert.count > 1 - { - // If we're expecting more than one revert, we need to reset the reverted_by address - // to latest reverter. - expected_revert.reverted_by = Some(call.target_address); + // Record current reverter address and call scheme before processing the expect revert + // if call reverted. + if outcome.result.is_revert() { + // 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) + { + expected_revert.reverted_by = Some(call.target_address); + } } if ecx.journaled_state.depth() <= expected_revert.depth { @@ -1444,20 +1475,16 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { }; if needs_processing { - // Only `remove` the expected revert from state if `expected_revert.count` == - // `expected_revert.actual_count` let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - - let handler_result = expect::handle_expect_revert( + return match revert_handlers::handle_expect_revert( cheatcode_call, false, - &mut expected_revert, + self.config.internal_expect_revert, + &expected_revert, outcome.result.result, outcome.result.output.clone(), &self.config.available_artifacts, - ); - - return match handler_result { + ) { Err(error) => { trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); outcome.result.result = InstructionResult::Revert; diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 3375ff0575..ffd67e1e54 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -60,7 +60,7 @@ pub use script::{Broadcast, Wallets, WalletsInner}; mod string; mod test; -pub use test::expect::{handle_expect_emit, handle_expect_revert}; +pub use test::expect::handle_expect_emit; mod toml; diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index ca85377f99..14fd496898 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -3,13 +3,13 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::Address; use alloy_sol_types::SolValue; -use chrono::DateTime; +use foundry_common::version::SEMVER_VERSION; use foundry_evm_core::constants::MAGIC_SKIP; -use std::env; pub(crate) mod assert; pub(crate) mod assume; pub(crate) mod expect; +pub(crate) mod revert_handlers; impl Cheatcode for zkVmCall { fn apply_stateful(&self, _ccx: &mut CheatsCtxt) -> Result { @@ -68,14 +68,7 @@ impl Cheatcode for breakpoint_1Call { impl Cheatcode for getFoundryVersionCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; - let cargo_version = env!("CARGO_PKG_VERSION"); - let git_sha = env!("VERGEN_GIT_SHA"); - let build_timestamp = DateTime::parse_from_rfc3339(env!("VERGEN_BUILD_TIMESTAMP")) - .expect("Invalid build timestamp format") - .format("%Y%m%d%H%M") - .to_string(); - let foundry_version = format!("{cargo_version}+{git_sha}+{build_timestamp}"); - Ok(foundry_version.abi_encode()) + Ok(SEMVER_VERSION.abi_encode()) } } diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index b4b6652ac2..a61cc4b2a2 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -1,7 +1,7 @@ use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{hex, I256, U256}; use foundry_evm_core::{ - abi::{format_units_int, format_units_uint}, + abi::console::{format_units_int, format_units_uint}, backend::GLOBAL_FAIL_SLOT, constants::CHEATCODE_ADDRESS, }; @@ -180,16 +180,16 @@ fn handle_assertion_result( match result { Ok(_) => Ok(Default::default()), Err(err) => { - let error_msg = error_msg.unwrap_or("assertion failed").to_string(); + let error_msg = error_msg.unwrap_or("assertion failed"); let msg = if format_error { format!("{error_msg}: {}", error_formatter(&err)) } else { - error_msg + error_msg.to_string() }; if ccx.state.config.assertions_revert { Err(msg.into()) } else { - executor.console_log(ccx, msg); + executor.console_log(ccx, &msg); ccx.ecx.sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; Ok(Default::default()) } diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs index a0321b5a1c..74bd79e096 100644 --- a/crates/cheatcodes/src/test/assume.rs +++ b/crates/cheatcodes/src/test/assume.rs @@ -1,12 +1,46 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; +use alloy_primitives::Address; use foundry_evm_core::constants::MAGIC_ASSUME; -use spec::Vm::{assumeCall, assumeNoRevertCall}; +use spec::Vm::{ + assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, PotentialRevert, +}; use std::fmt::Debug; #[derive(Clone, Debug)] pub struct AssumeNoRevert { /// The call depth at which the cheatcode was added. pub depth: u64, + /// Acceptable revert parameters for the next call, to be thrown out if they are encountered; + /// reverts with parameters not specified here will count as normal reverts and not rejects + /// towards the counter. + pub reasons: Vec, + /// Address that reverted the call. + pub reverted_by: Option
, +} + +/// Parameters for a single anticipated revert, to be thrown out if encountered. +#[derive(Clone, Debug)] +pub struct AcceptableRevertParameters { + /// The expected revert data returned by the revert + pub reason: Vec, + /// If true then only the first 4 bytes of expected data returned by the revert are checked. + pub partial_match: bool, + /// Contract expected to revert next call. + pub reverter: Option
, +} + +impl AcceptableRevertParameters { + fn from(potential_revert: &PotentialRevert) -> Self { + Self { + reason: potential_revert.revertData.to_vec(), + partial_match: potential_revert.partialMatch, + reverter: if potential_revert.reverter == Address::ZERO { + None + } else { + Some(potential_revert.reverter) + }, + } + } } impl Cheatcode for assumeCall { @@ -20,10 +54,45 @@ impl Cheatcode for assumeCall { } } -impl Cheatcode for assumeNoRevertCall { +impl Cheatcode for assumeNoRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - ccx.state.assume_no_revert = - Some(AssumeNoRevert { depth: ccx.ecx.journaled_state.depth() }); - Ok(Default::default()) + assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth(), vec![]) } } + +impl Cheatcode for assumeNoRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { potentialRevert } = self; + assume_no_revert( + ccx.state, + ccx.ecx.journaled_state.depth(), + vec![AcceptableRevertParameters::from(potentialRevert)], + ) + } +} + +impl Cheatcode for assumeNoRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { potentialReverts } = self; + assume_no_revert( + ccx.state, + ccx.ecx.journaled_state.depth(), + potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), + ) + } +} + +fn assume_no_revert( + state: &mut Cheatcodes, + depth: u64, + parameters: Vec, +) -> Result { + ensure!( + state.assume_no_revert.is_none(), + "you must make another external call prior to calling assumeNoRevert again" + ); + + state.assume_no_revert = Some(AssumeNoRevert { depth, reasons: parameters, reverted_by: None }); + + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 48791e1552..0027d5dd6f 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -2,29 +2,13 @@ use std::collections::VecDeque; use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; use alloy_primitives::{ - address, hex, map::{hash_map::Entry, AddressHashMap, HashMap}, Address, Bytes, LogData as RawLog, U256, }; -use alloy_sol_types::{SolError, SolValue}; -use foundry_cheatcodes_common::expect::{ExpectedCallData, ExpectedCallType}; -use foundry_common::ContractsByArtifact; -use foundry_evm_core::decode::RevertDecoder; -use revm::interpreter::{ - return_ok, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, -}; -use spec::Vm; - -/// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. -/// Solidity will see a successful call and attempt to decode the return data. Therefore, we need -/// to populate the return with dummy bytes so the decode doesn't fail. -/// -/// 8192 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in -/// size. -static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]); +use revm::interpreter::{InstructionResult, Interpreter, InterpreterAction, InterpreterResult}; -/// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. -const DUMMY_CREATE_ADDRESS: Address = address!("0000000000000000000000000000000000000001"); +use super::revert_handlers::RevertParameters; +use foundry_cheatcodes_common::expect::{ExpectedCallData, ExpectedCallType}; /// The type of expected revert. #[derive(Clone, Debug)] @@ -51,8 +35,10 @@ pub struct ExpectedRevert { pub partial_match: bool, /// Contract expected to revert next call. pub reverter: Option
, - /// Actual reverter of the call. + /// Address that reverted the call. pub reverted_by: Option
, + /// Max call depth reached during next call execution. + pub max_depth: u64, /// Number of times this revert is expected. pub count: u64, /// Actual number of times this revert has been seen. @@ -569,6 +555,20 @@ impl Cheatcode for expectSafeMemoryCallCall { } } +impl RevertParameters for ExpectedRevert { + fn reverter(&self) -> Option
{ + self.reverter + } + + fn reason(&self) -> Option<&[u8]> { + self.reason.as_deref() + } + + fn partial_match(&self) -> bool { + self.partial_match + } +} + /// Handles expected calls specified by the `expectCall` cheatcodes. /// /// It can handle calls in two ways: @@ -878,142 +878,13 @@ fn expect_revert( partial_match, reverter, reverted_by: None, + max_depth: depth, count, actual_count: 0, }); Ok(Default::default()) } -pub fn handle_expect_revert( - is_cheatcode: bool, - is_create: bool, - expected_revert: &mut ExpectedRevert, - status: InstructionResult, - retdata: Bytes, - known_contracts: &Option, -) -> Result<(Option
, Bytes)> { - let success_return = || { - if is_create { - (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) - } else { - (None, DUMMY_CALL_OUTPUT.clone()) - } - }; - - let stringify = |data: &[u8]| { - if let Ok(s) = String::abi_decode(data, true) { - return s; - } - if data.is_ascii() { - return std::str::from_utf8(data).unwrap().to_owned(); - } - hex::encode_prefixed(data) - }; - - if expected_revert.count == 0 { - if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { - ensure!( - matches!(status, return_ok!()), - "call reverted when it was expected not to revert" - ); - return Ok(success_return()); - } - - // Flags to track if the reason and reverter match. - 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 { - 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); - - 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() - )), - _ => Ok(success_return()), - } - } else { - ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); - - // If expected reverter address is set then check it matches the actual reverter. - if let (Some(expected_reverter), Some(actual_reverter)) = - (expected_revert.reverter, expected_revert.reverted_by) - { - if expected_reverter != actual_reverter { - return Err(fmt_err!( - "Reverter != expected reverter: {} != {}", - actual_reverter, - expected_reverter - )); - } - } - - let expected_reason = expected_revert.reason.as_deref(); - // If None, accept any revert. - let Some(expected_reason) = expected_reason else { - return Ok(success_return()); - }; - - if !expected_reason.is_empty() && retdata.is_empty() { - bail!("call reverted as expected, but without data"); - } - - let mut actual_revert: Vec = retdata.into(); - - // Compare only the first 4 bytes if partial match. - if expected_revert.partial_match && actual_revert.get(..4) == expected_reason.get(..4) { - return Ok(success_return()) - } - - // 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()) - { - Ok(success_return()) - } else { - let (actual, expected) = if let Some(contracts) = known_contracts { - let decoder = RevertDecoder::new().with_abis(contracts.iter().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)) - }; - Err(fmt_err!("Error != expected error: {} != {}", actual, expected,)) - } - } -} - fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool { if log.topics().len() != expected.topics().len() { return false @@ -1045,15 +916,3 @@ fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) offsets.push(start..end); Ok(Default::default()) } - -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..], false) { - return decoded; - } - } - revert -} diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs new file mode 100644 index 0000000000..4368d0dc2a --- /dev/null +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -0,0 +1,243 @@ +use crate::{Error, Result}; +use alloy_primitives::{address, hex, Address, Bytes}; +use alloy_sol_types::{SolError, SolValue}; +use foundry_common::ContractsByArtifact; +use foundry_evm_core::decode::RevertDecoder; +use revm::interpreter::{return_ok, InstructionResult}; +use spec::Vm; + +use super::{ + assume::{AcceptableRevertParameters, AssumeNoRevert}, + expect::ExpectedRevert, +}; + +/// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. +/// Solidity will see a successful call and attempt to decode the return data. Therefore, we need +/// to populate the return with dummy bytes so the decode doesn't fail. +/// +/// 8192 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in +/// size. +static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]); + +/// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. +const DUMMY_CREATE_ADDRESS: Address = address!("0000000000000000000000000000000000000001"); + +fn stringify(data: &[u8]) -> String { + if let Ok(s) = String::abi_decode(data, true) { + return s; + } + if data.is_ascii() { + return std::str::from_utf8(data).unwrap().to_owned(); + } + hex::encode_prefixed(data) +} + +/// Common parameters for expected or assumed reverts. Allows for code reuse. +pub(crate) trait RevertParameters { + fn reverter(&self) -> Option
; + fn reason(&self) -> Option<&[u8]>; + fn partial_match(&self) -> bool; +} + +impl RevertParameters for AcceptableRevertParameters { + fn reverter(&self) -> Option
{ + self.reverter + } + + fn reason(&self) -> Option<&[u8]> { + Some(&self.reason) + } + + fn partial_match(&self) -> bool { + self.partial_match + } +} + +/// Core logic for handling reverts that may or may not be expected (or assumed). +fn handle_revert( + is_cheatcode: bool, + revert_params: &impl RevertParameters, + status: InstructionResult, + retdata: &Bytes, + known_contracts: &Option, + reverter: Option<&Address>, +) -> 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) + { + if expected_reverter != actual_reverter { + return Err(fmt_err!( + "Reverter != expected reverter: {} != {}", + actual_reverter, + expected_reverter + )); + } + } + + let expected_reason = revert_params.reason(); + // If None, accept any revert. + let Some(expected_reason) = expected_reason else { + return Ok(()); + }; + + if !expected_reason.is_empty() && retdata.is_empty() { + bail!("call reverted as expected, but without data"); + } + + let mut actual_revert: Vec = retdata.to_vec(); + + // Compare only the first 4 bytes if partial match. + if revert_params.partial_match() && actual_revert.get(..4) == expected_reason.get(..4) { + return Ok(()); + } + + // 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()) + { + Ok(()) + } else { + let (actual, expected) = if let Some(contracts) = known_contracts { + let decoder = RevertDecoder::new().with_abis(contracts.iter().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)) + }; + Err(fmt_err!("Error != expected error: {} != {}", actual, expected,)) + } +} + +pub(crate) fn handle_assume_no_revert( + assume_no_revert: &AssumeNoRevert, + status: InstructionResult, + retdata: &Bytes, + known_contracts: &Option, +) -> Result<()> { + // if a generic AssumeNoRevert, return Ok(). Otherwise, iterate over acceptable reasons and try + // to match against any, otherwise, return an Error with the revert data + if assume_no_revert.reasons.is_empty() { + Ok(()) + } else { + assume_no_revert + .reasons + .iter() + .find_map(|reason| { + handle_revert( + false, + reason, + status, + retdata, + known_contracts, + assume_no_revert.reverted_by.as_ref(), + ) + .ok() + }) + .ok_or_else(|| retdata.clone().into()) + } +} + +pub(crate) fn handle_expect_revert( + is_cheatcode: bool, + is_create: bool, + internal_expect_revert: bool, + expected_revert: &ExpectedRevert, + status: InstructionResult, + retdata: Bytes, + known_contracts: &Option, +) -> Result<(Option
, Bytes)> { + let success_return = || { + if is_create { + (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) + } else { + (None, DUMMY_CALL_OUTPUT.clone()) + } + }; + + // Check depths if it's not an expect cheatcode call and if internal expect reverts not enabled. + if !is_cheatcode && !internal_expect_revert { + ensure!( + expected_revert.max_depth > expected_revert.depth, + "call didn't revert at a lower depth than cheatcode call depth" + ); + } + + if expected_revert.count == 0 { + if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { + ensure!( + matches!(status, return_ok!()), + "call reverted when it was expected not to revert" + ); + return Ok(success_return()); + } + + // Flags to track if the reason and reverter match. + 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 { + 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); + + 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() + )), + _ => Ok(success_return()), + } + } else { + ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); + + handle_revert( + is_cheatcode, + expected_revert, + status, + &retdata, + known_contracts, + expected_revert.reverted_by.as_ref(), + )?; + Ok(success_return()) + } +} + +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..], false) { + return decoded; + } + } + revert +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index fbb61fc691..a7f6a7facf 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -20,17 +20,9 @@ workspace = true name = "chisel" path = "bin/main.rs" -[build-dependencies] -vergen = { workspace = true, default-features = false, features = [ - "build", - "git", - "gitcl", -] } - [dependencies] # forge forge-fmt.workspace = true -foundry-block-explorers.workspace = true foundry-cli.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util", "full"] } @@ -46,10 +38,9 @@ alloy-primitives = { workspace = true, features = [ "rlp", ] } alloy-json-abi.workspace = true -alloy-rpc-types.workspace = true clap = { version = "4", features = ["derive", "env", "wrap_help"] } -dirs = "5" +dirs.workspace = true eyre.workspace = true regex.workspace = true reqwest.workspace = true @@ -65,6 +56,7 @@ time = { version = "0.3", features = ["formatting"] } tokio = { workspace = true, features = ["full"] } yansi.workspace = true tracing.workspace = true +walkdir.workspace = true [target.'cfg(unix)'.dependencies] tikv-jemallocator = { workspace = true, optional = true } diff --git a/crates/chisel/README.md b/crates/chisel/README.md deleted file mode 100644 index 8ea2840aa3..0000000000 --- a/crates/chisel/README.md +++ /dev/null @@ -1,216 +0,0 @@ -# `chisel` - -Chisel is a fast, utilitarian, and verbose Solidity REPL. It is heavily inspired by the incredible work done in [soli](https://github.com/jpopesculian/soli) and [solidity-shell](https://github.com/tintinweb/solidity-shell)! - -![preview](./assets/preview.gif) - -## Why? - -Ever wanted to quickly test a small feature in solidity? - -Perhaps to test how custom errors work, or how to write inline assembly? - -Chisel is a fully-functional Solidity REPL, allowing you to write, execute, and debug Solidity directly in the command line. - -Once you finish testing, Chisel even lets you export your code to a new solidity file! - -In this sense, Chisel even serves as a Foundry script generator. - -## Feature Completion - -[soli](https://github.com/jpopesculian/soli) and [solidity-shell](https://github.com/tintinweb/solidity-shell) both provide a great solidity REPL, achieving: - -- Statement support -- Custom events, errors, functions, imports -- Inspecting variables -- Forking remote chains -- Session caching - -Chisel aims to improve upon existing Solidity REPLs by integrating with foundry as well as offering additional functionality: - -- More verbose variable / state inspection -- Improved error messages -- Foundry-style call traces -- In-depth environment configuration -- ... and many more future features! - -### Migrating from [soli](https://github.com/jpopesculian/soli) or [solidity-shell](https://github.com/tintinweb/solidity-shell) - -Migration from existing Solidity REPLs such as [soli](https://github.com/jpopesculian/soli) or [solidity-shell](https://github.com/tintinweb/solidity-shell) is as -simple as installing Chisel via `foundryup`. For information on features, usage, and configuration, see the [Usage](#usage) section as well as the chisel manpage (`man chisel` or `chisel --help`). - -## Installation - -To install `chisel`, simply run `foundryup`! - -If you do not have `foundryup` installed, reference the Foundry [installation guide](../../README.md#installation). - -## Usage - -### REPL Commands - -```text -⚒️ Chisel help -============= -General - !help | !h - Display all commands - !quit | !q - Quit Chisel - !exec [args] | !e [args] - Execute a shell command and print the output - -Session - !clear | !c - Clear current session source - !source | !so - Display the source code of the current session - !save [id] | !s [id] - Save the current session to cache - !load | !l - Load a previous session ID from cache - !list | !ls - List all cached sessions - !clearcache | !cc - Clear the chisel cache of all stored sessions - !export | !ex - Export the current session source to a script file - !fetch | !fe - Fetch the interface of a verified contract on Etherscan - !edit - Open the current session in an editor - -Environment - !fork | !f - Fork an RPC for the current session. Supply 0 arguments to return to a local network - !traces | !t - Enable / disable traces for the current session - !calldata [data] | !cd [data] - Set calldata (`msg.data`) for the current session (appended after function selector). Clears it if no argument provided. - -Debug - !memdump | !md - Dump the raw memory of the current state - !stackdump | !sd - Dump the raw stack of the current state - !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. -``` - -### Cache Session - -While chisel sessions are not persistent by default, they can be saved to the cache via the builtin `save` command from within the REPL. - -Sessions can also be named by supplying a single argument to the `save` command, i.e. `!save my_session`. - -```text -$ chisel -➜ uint a = 1; -➜ uint b = a << 0x08; -➜ !save -Saved session to cache with ID = 0. -``` - -### Loading a Previous Session - -Chisel allows you to load a previous session from your history. - -To view your history, you can run `chisel list` or `!list`. This will print a list of your previous sessions, identifiable by their index. - -You can also run `chisel view ` or `!view ` to view the contents of a specific session. - -To load a session, run `chisel load ` or use the `!load ` where `` is a valid session index (eg 2 in the example below). - -```text -$ chisel list -⚒️ Chisel Sessions -"2022-10-27 14:46:29" - chisel-0.json -"2022-10-27 14:46:29" - chisel-1.json -$ chisel view 1 -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.17; - -contract REPL { - event KeccakEvent(bytes32 hash); - - function run() public { - emit KeccakEvent(keccak256(abi.encode("Hello, world!"))); - } -} -$ chisel load 1 -➜ ... -``` - -### Clearing the Cache - -To clear Chisel's cache (stored in `~/.foundry/cache/chisel`), use the `chisel clear-cache` or `!clearcache` command. - -```text -➜ !clearcache -Cleared chisel cache! -``` - -### Toggling Traces - -By default, traces will only be shown if an input causes the call to the REPL contract to revert. To turn traces on -regardless of the call result, use the `!traces` command or pass in a verbosity option of any level (`-v`) to -the chisel binary. - -```text -➜ uint a -➜ contract Test { - function get() external view returns (uint) { - return 256; - } -} -➜ Test t = new Test() -➜ !traces -Successfully enabled traces! -➜ a = t.get() -Traces: - [69808] 0xBd770416a3345F91E4B34576cb804a576fa48EB1::run() - ├─ [36687] → new @0xf4D9599aFd90B5038b18e3B551Bc21a97ed21c37 - │ └─ ← 183 bytes of code - ├─ [315] 0xf4D9599aFd90B5038b18e3B551Bc21a97ed21c37::get() [staticcall] - │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000100 - └─ ← () - -➜ a -Type: uint -├ Hex: 0x100 -└ Decimal: 256 -``` - -### Forking a Network - -To fork a network within your chisel session, use the `!fork ` command or supply a `--fork-url ` flag -to the chisel binary. The `!fork` command also accepts aliases from the `[rpc_endpoints]` section of your `foundry.toml` -if chisel was launched in the root of a foundry project (ex. `!fork mainnet`), as well as interpolated environment variables -(ex. `!fork https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`). - -### Fetching an Interface of a Verified Contract - -To fetch an interface of a verified contract on Etherscan, use the `!fetch` / `!f` command. - -> **Note** -> At the moment, only contracts that are deployed and verified on mainnet can be fetched. Support for other -> networks with Etherscan explorers coming soon. - -```text -➜ !fetch 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 IWETH -Added 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2's interface to source as `IWETH` -``` - -### Executing a Shell Command - -Shell commands can be executed within Chisel with the `!exec` / `!e` command. - -```text -➜ !e ls -anvil -binder -Cargo.lock -Cargo.toml -cast -chisel -cli -common -config -CONTRIBUTING.md -Dockerfile -docs -evm -fmt -forge -foundryup -LICENSE-APACHE -LICENSE-MIT -README.md -rustfmt.toml -target -testdata -ui -utils -``` diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 797da8a122..93b93d46e1 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -14,7 +14,11 @@ use foundry_cli::{ opts::{BuildOpts, GlobalArgs}, utils::{self, LoadConfig}, }; -use foundry_common::{evm::EvmArgs, fs}; +use foundry_common::{ + evm::EvmArgs, + fs, + version::{LONG_VERSION, SHORT_VERSION}, +}; use foundry_config::{ figment::{ value::{Dict, Map}, @@ -37,18 +41,9 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(Chisel, build, evm); -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); - /// Fast, utilitarian, and verbose Solidity REPL. #[derive(Debug, Parser)] -#[command(name = "chisel", version = VERSION_MESSAGE)] +#[command(name = "chisel", version = SHORT_VERSION, long_version = LONG_VERSION)] pub struct Chisel { /// Include the global arguments. #[command(flatten)] diff --git a/crates/chisel/build.rs b/crates/chisel/build.rs deleted file mode 100644 index c2f550fb6f..0000000000 --- a/crates/chisel/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index b66c61bed5..42ca1127a3 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -338,7 +338,6 @@ impl SessionSource { self.config.evm_opts.clone(), None, None, - Some(self.solc.version.clone()), strategy.runner.new_cheatcode_inspector_strategy(strategy.context.as_mut()), ) .into(), diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index ccae2db2dd..5eeedada69 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -1,23 +1,17 @@ -#![doc = include_str!("../README.md")] +//! Chisel is a fast, utilitarian, and verbose Solidity REPL. + #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate foundry_common; -pub mod dispatcher; - pub mod cmd; - +pub mod dispatcher; +pub mod executor; pub mod history; - +pub mod runner; pub mod session; - pub mod session_source; - -pub mod runner; - -pub mod executor; - pub mod solidity_helper; pub mod prelude { diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index 128873167a..f6e36395c5 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -17,6 +17,7 @@ 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. @@ -466,15 +467,26 @@ contract {contract_name} is Script {{ 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 (vm_import, vm_constant) = if !config.no_vm { - ( - "import {Vm} from \"forge-std/Vm.sol\";\n", - "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n" - ) - } else { - ("", "") - }; + 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#" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e131b5dad0..df38ac6b0d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -39,6 +39,7 @@ dotenvy = "0.15" eyre.workspace = true futures.workspace = true indicatif = "0.17" +itertools.workspace = true rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 49c46d83fb..d5d2037aa7 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,12 +1,42 @@ use eyre::EyreHandler; +use itertools::Itertools; use std::{error::Error, fmt}; -/// A custom context type for Foundry specific error reporting via `eyre` -#[derive(Debug)] -pub struct Handler; +/// A custom context type for Foundry specific error reporting via `eyre`. +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 } + } + + /// Override the debug handler with a custom one. + pub fn debug_handler(mut self, debug_handler: Option>) -> Self { + self.debug_handler = debug_handler; + self + } +} 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) + } + fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(debug_handler) = &self.debug_handler { + return debug_handler.debug(error, f); + } + if f.alternate() { return fmt::Debug::fmt(error, f) } @@ -31,9 +61,15 @@ impl EyreHandler for Handler { Ok(()) } + + fn track_caller(&mut self, location: &'static std::panic::Location<'static>) { + if let Some(debug_handler) = &mut self.debug_handler { + debug_handler.track_caller(location); + } + } } -/// Installs the Foundry [eyre] and [panic](mod@std::panic) hooks as the global ones. +/// Installs the Foundry [`eyre`] and [`panic`](mod@std::panic) hooks as the global ones. /// /// # Details /// @@ -49,15 +85,14 @@ pub fn install() { let panic_section = "This is a bug. Consider reporting it at https://github.com/matter-labs/foundry-zksync"; - let (panic_hook, debug_eyre_hook) = + let (panic_hook, debug_hook) = color_eyre::config::HookBuilder::default().panic_section(panic_section).into_hooks(); panic_hook.install(); - let eyre_install_result = if std::env::var_os("FOUNDRY_DEBUG").is_some() { - debug_eyre_hook.install() - } else { - eyre::set_hook(Box::new(|_| Box::new(Handler))) - }; - if let Err(e) = eyre_install_result { + let debug_hook = debug_hook.into_eyre_hook(); + let debug = std::env::var_os("FOUNDRY_DEBUG").is_some(); + if let Err(e) = eyre::set_hook(Box::new(move |e| { + Box::new(Handler::new().debug_handler(debug.then(|| debug_hook(e)))) + })) { debug!("failed to install eyre error hook: {e}"); } } diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index f69fee01f6..a751badefa 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -152,7 +152,7 @@ impl BuildOpts { /// `find_project_root` and merges the cli `BuildArgs` into it before returning /// [`foundry_config::Config::project()`]). pub fn project(&self) -> Result> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; Ok(config.project()?) } @@ -203,19 +203,6 @@ impl<'a> From<&'a BuildOpts> for Figment { } } -impl<'a> From<&'a BuildOpts> for Config { - fn from(args: &'a BuildOpts) -> Self { - let figment: Figment = args.into(); - let mut config = Self::from_provider(figment).sanitized(); - // if `--config-path` is set we need to adjust the config's root path to the actual root - // path for the project, otherwise it will the parent dir of the `--config-path` - if args.project_paths.config_path.is_some() { - config.root = args.project_paths.project_root(); - } - config - } -} - impl Provider for BuildOpts { fn metadata(&self) -> Metadata { Metadata::named("Core Build Args Provider") diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index 7a4d83eeaf..263e03c148 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -71,8 +71,11 @@ impl ProjectPathOpts { /// # Panics /// /// Panics if the project root directory cannot be found. See [`find_project_root`]. + #[track_caller] pub fn project_root(&self) -> PathBuf { - self.root.clone().unwrap_or_else(|| find_project_root(None)) + self.root + .clone() + .unwrap_or_else(|| find_project_root(None).expect("could not determine project root")) } /// Returns the remappings to add to the config diff --git a/crates/cli/src/opts/dependency.rs b/crates/cli/src/opts/dependency.rs index 6783e32d25..c3d802dbe2 100644 --- a/crates/cli/src/opts/dependency.rs +++ b/crates/cli/src/opts/dependency.rs @@ -8,7 +8,7 @@ static GH_REPO_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"[\w-]+/[\w /// Git repo prefix regex pub static GH_REPO_PREFIX_REGEX: LazyLock = LazyLock::new(|| { - Regex::new(r"((git@)|(git\+https://)|(https://)|(org-([A-Za-z0-9-])+@))?(?P[A-Za-z0-9-]+)\.(?P[A-Za-z0-9-]+)(/|:)") + Regex::new(r"((git@)|(git\+https://)|(https://)|https://(?P[^@]+)@|(org-([A-Za-z0-9-])+@))?(?P[A-Za-z0-9-]+)\.(?P[A-Za-z0-9-]+)(/|:)") .unwrap() }); @@ -81,7 +81,15 @@ impl FromStr for Dependency { let brand = captures.name("brand").unwrap().as_str(); let tld = captures.name("tld").unwrap().as_str(); let project = GH_REPO_PREFIX_REGEX.replace(dependency, ""); - Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))) + if let Some(token) = captures.name("token") { + Some(format!( + "https://{}@{brand}.{tld}/{}", + token.as_str(), + project.trim_end_matches(".git") + )) + } else { + Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))) + } } else { // If we don't have a URL and we don't have a valid // GitHub repository name, then we assume this is the alias. @@ -120,7 +128,7 @@ impl FromStr for Dependency { let url = url.to_string(); let name = url .split('/') - .last() + .next_back() .ok_or_else(|| eyre::eyre!("no dependency name found"))? .to_string(); @@ -392,4 +400,18 @@ mod tests { assert_eq!(dep.tag, Some("80eb41b".to_string())); assert_eq!(dep.alias, None); } + + #[test] + fn can_parse_https_with_github_token() { + // + let dep = Dependency::from_str( + "https://ghp_mytoken@github.com/private-org/precompiles-solidity.git", + ) + .unwrap(); + assert_eq!(dep.name, "precompiles-solidity"); + assert_eq!( + dep.url, + Some("https://ghp_mytoken@github.com/private-org/precompiles-solidity".to_string()) + ); + } } diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index 6dc34067f6..665c504ab9 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -1,5 +1,8 @@ use clap::{ArgAction, Parser}; -use foundry_common::shell::{ColorChoice, OutputFormat, OutputMode, Shell, Verbosity}; +use foundry_common::{ + shell::{ColorChoice, OutputFormat, OutputMode, Shell, Verbosity}, + version::{IS_NIGHTLY_VERSION, NIGHTLY_VERSION_WARNING_MESSAGE}, +}; use serde::{Deserialize, Serialize}; /// Global arguments for the CLI. @@ -47,6 +50,14 @@ impl GlobalArgs { self.force_init_thread_pool()?; } + // 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 + { + let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE); + } + Ok(()) } diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 13f395f268..423a765b20 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -235,58 +235,46 @@ pub fn has_batch_support(chain_id: u64) -> bool { /// Helpers for loading configuration. /// -/// This is usually implicitly implemented on a "&CmdArgs" struct via impl macros defined in -/// `forge_config` (see [`foundry_config::impl_figment_convert`] for more details) and the impl -/// definition on `T: Into + Into` below. +/// This is usually implemented through the macros defined in [`foundry_config`]. See +/// [`foundry_config::impl_figment_convert`] for more details. /// -/// Each function also has an `emit_warnings` form which does the same thing as its counterpart but -/// also prints `Config::__warnings` to stderr +/// By default each function will emit warnings generated during loading, unless the `_no_warnings` +/// variant is used. pub trait LoadConfig { - /// Load and sanitize the [`Config`] based on the options provided in self - /// - /// Returns an error if loading the config failed - fn try_load_config(self) -> Result; - /// Load and sanitize the [`Config`] based on the options provided in self - fn load_config(self) -> Config; - /// Load and sanitize the [`Config`], as well as extract [`EvmOpts`] from self - fn load_config_and_evm_opts(self) -> Result<(Config, EvmOpts)>; - /// Load [`Config`] but do not sanitize. See [`Config::sanitized`] for more information - fn load_config_unsanitized(self) -> Config; + /// Load the [`Config`] based on the options provided in self. + fn figment(&self) -> Figment; + + /// Load and sanitize the [`Config`] based on the options provided in self. + fn load_config(&self) -> Result { + self.load_config_no_warnings().inspect(emit_warnings) + } + + /// Same as [`LoadConfig::load_config`] but does not emit warnings. + fn load_config_no_warnings(&self) -> Result { + self.load_config_unsanitized_no_warnings().map(Config::sanitized) + } + /// Load [`Config`] but do not sanitize. See [`Config::sanitized`] for more information. - /// - /// Returns an error if loading failed - fn try_load_config_unsanitized(self) -> Result; - /// Same as [`LoadConfig::load_config`] but also emits warnings generated - fn load_config_emit_warnings(self) -> Config; - /// Same as [`LoadConfig::load_config`] but also emits warnings generated - /// - /// Returns an error if loading failed - fn try_load_config_emit_warnings(self) -> Result; - /// Same as [`LoadConfig::load_config_and_evm_opts`] but also emits warnings generated - fn load_config_and_evm_opts_emit_warnings(self) -> Result<(Config, EvmOpts)>; - /// Same as [`LoadConfig::load_config_unsanitized`] but also emits warnings generated - fn load_config_unsanitized_emit_warnings(self) -> Config; - fn try_load_config_unsanitized_emit_warnings(self) -> Result; -} + fn load_config_unsanitized(&self) -> Result { + self.load_config_unsanitized_no_warnings().inspect(emit_warnings) + } -impl LoadConfig for T -where - T: Into + Into, -{ - fn try_load_config(self) -> Result { - let figment: Figment = self.into(); - Ok(Config::try_from(figment)?.sanitized()) + /// Same as [`LoadConfig::load_config_unsanitized`] but also emits warnings generated + fn load_config_unsanitized_no_warnings(&self) -> Result { + Config::from_provider(self.figment()) } - fn load_config(self) -> Config { - self.into() + /// Load and sanitize the [`Config`], as well as extract [`EvmOpts`] from self + fn load_config_and_evm_opts(&self) -> Result<(Config, EvmOpts)> { + self.load_config_and_evm_opts_no_warnings().inspect(|(config, _)| emit_warnings(config)) } - fn load_config_and_evm_opts(self) -> Result<(Config, EvmOpts)> { - let figment: Figment = self.into(); + /// Same as [`LoadConfig::load_config_and_evm_opts`] but also emits warnings generated + fn load_config_and_evm_opts_no_warnings(&self) -> Result<(Config, EvmOpts)> { + let figment = self.figment(); let mut evm_opts = figment.extract::().map_err(ExtractConfigError::new)?; - let config = Config::try_from(figment)?.sanitized(); + let config = Config::from_provider(figment)?.sanitized(); // update the fork url if it was an alias if let Some(fork_url) = config.get_rpc_url() { @@ -296,45 +284,14 @@ where Ok((config, evm_opts)) } +} - fn load_config_unsanitized(self) -> Config { - let figment: Figment = self.into(); - Config::from_provider(figment) - } - - fn try_load_config_unsanitized(self) -> Result { - let figment: Figment = self.into(); - Config::try_from(figment) - } - - fn load_config_emit_warnings(self) -> Config { - let config = self.load_config(); - config.warnings.iter().for_each(|w| sh_warn!("{w}").unwrap()); - config - } - - fn try_load_config_emit_warnings(self) -> Result { - let config = self.try_load_config()?; - emit_warnings(&config); - Ok(config) - } - - fn load_config_and_evm_opts_emit_warnings(self) -> Result<(Config, EvmOpts)> { - let (config, evm_opts) = self.load_config_and_evm_opts()?; - emit_warnings(&config); - Ok((config, evm_opts)) - } - - fn load_config_unsanitized_emit_warnings(self) -> Config { - let config = self.load_config_unsanitized(); - emit_warnings(&config); - config - } - - fn try_load_config_unsanitized_emit_warnings(self) -> Result { - let config = self.try_load_config_unsanitized()?; - emit_warnings(&config); - Ok(config) +impl LoadConfig for T +where + for<'a> Figment: From<&'a T>, +{ + fn figment(&self) -> Figment { + self.into() } } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 1adbec4358..b9eaf5c133 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -212,7 +212,7 @@ pub fn load_dotenv() { // we only want the .env file of the cwd and project root // `find_project_root` calls `current_dir` internally so both paths are either both `Ok` or // both `Err` - if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), try_find_project_root(None)) { + if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), find_project_root(None)) { load(&prj_root); if cwd != prj_root { // prj root and cwd can be identical diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ce688416e4..9e067fa67e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -72,6 +72,15 @@ anstream.workspace = true anstyle.workspace = true terminal_size.workspace = true +[build-dependencies] +chrono.workspace = true +vergen = { workspace = true, features = [ + "build", + "git", + "gitcl", +] } + [dev-dependencies] similar-asserts.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +axum = { workspace = true } \ No newline at end of file diff --git a/crates/common/build.rs b/crates/common/build.rs new file mode 100644 index 0000000000..54890ffdaf --- /dev/null +++ b/crates/common/build.rs @@ -0,0 +1,90 @@ +use std::{env, error::Error}; + +use chrono::DateTime; +use vergen::EmitBuilder; + +#[allow(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 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. + 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(); + + // Set the 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. + // - The latest version from Cargo.toml. + // - The short SHA of the latest commit. + // - The UNIX formatted build timestamp. + // - 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}" + ); + + // 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})"); + + // The long version information for the Foundry CLI. + // - The latest version from Cargo.toml. + // - The long SHA of the latest commit. + // - The build timestamp in RFC3339 format and UNIX format in seconds. + // - The build profile. + // + // Example: + // + // ```text + // + // Version: 0.3.0-dev + // Commit SHA: 5186142d3bb4d1be7bb4ade548b77c8e2270717e + // 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}"); + + Ok(()) +} diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index bf9b530e04..a2dcfdf3ad 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -168,7 +168,7 @@ impl ProjectCompiler { /// /// ```ignore /// use foundry_common::compile::ProjectCompiler; - /// let config = foundry_config::Config::load(); + /// let config = foundry_config::Config::load().unwrap(); /// let prj = config.project().unwrap(); /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); /// ``` diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index bccac2f786..9aac912d4f 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -259,6 +259,15 @@ impl ContractsByArtifact { 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()) + } + /// Flattens the contracts into functions, events and errors. pub fn flatten(&self) -> (BTreeMap, BTreeMap, JsonAbi) { let mut funcs = BTreeMap::new(); diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs index 1c4eadeac5..650cb068a3 100644 --- a/crates/common/src/ens.rs +++ b/crates/common/src/ens.rs @@ -96,10 +96,15 @@ impl FromStr for NameOrAddress { type Err =
::Err; fn from_str(s: &str) -> Result { - if let Ok(addr) = Address::from_str(s) { - Ok(Self::Address(addr)) - } else { - Ok(Self::Name(s.to_string())) + match Address::from_str(s) { + Ok(addr) => Ok(Self::Address(addr)), + Err(err) => { + if s.contains('.') { + Ok(Self::Name(s.to_string())) + } else { + Err(err) + } + } } } } @@ -236,4 +241,16 @@ mod test { assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}"); } } + + #[test] + fn test_invalid_address() { + for addr in [ + "0x314618", + "0x000000000000000000000000000000000000000", // 41 + "0x00000000000000000000000000000000000000000", // 43 + "0x28679A1a632125fbBf7A68d850E50623194A709E123", // 44 + ] { + assert!(NameOrAddress::from_str(addr).is_err()); + } + } } diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index dac5e6d9ad..493d638162 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -267,6 +267,7 @@ pub struct EnvArgs { /// Whether to disable the block gas limit checks. #[arg(long, visible_alias = "no-gas-limit")] + #[serde(skip_serializing_if = "std::ops::Not::not")] pub disable_block_gas_limit: bool, } @@ -300,7 +301,7 @@ mod tests { env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() }, ..Default::default() }; - let config = Config::from_provider(Config::figment().merge(args)); + 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"]); @@ -313,7 +314,7 @@ mod tests { env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() }, ..Default::default() }; - let config = Config::from_provider(Config::figment().merge(args)); + 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"]); @@ -328,7 +329,7 @@ mod tests { let env = EnvArgs::parse_from(["foundry-common", "--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)); + let config = Config::from_provider(Config::figment().merge(args)).unwrap(); assert_eq!(config.chain, Some(Chain::mainnet())); } } diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 71a62d13a7..3d061759c5 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -1,4 +1,5 @@ -//! Contains various `std::fs` wrapper functions that also contain the target path in their errors +//! 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 std::{ @@ -7,7 +8,8 @@ use std::{ path::{Component, Path, PathBuf}, }; -type Result = std::result::Result; +/// The [`fs`](self) result type. +pub type Result = std::result::Result; /// Wrapper for [`File::create`]. pub fn create_file(path: impl AsRef) -> Result { diff --git a/crates/common/src/io/shell.rs b/crates/common/src/io/shell.rs index fd667ea682..19b3ae07e7 100644 --- a/crates/common/src/io/shell.rs +++ b/crates/common/src/io/shell.rs @@ -18,6 +18,11 @@ use std::{ }, }; +/// Returns the current color choice. +pub fn color_choice() -> ColorChoice { + Shell::get().color_choice() +} + /// Returns the currently set verbosity level. pub fn verbosity() -> Verbosity { Shell::get().verbosity() diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 259e8fee2d..88643a67c8 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -34,6 +34,7 @@ pub mod term; pub mod traits; pub mod transactions; mod utils; +pub mod version; pub use constants::*; pub use contracts::*; diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 563cec3138..e94d2f6933 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -176,7 +176,7 @@ impl RuntimeTransport { ); } - if !headers.iter().any(|(k, _v)| k.as_str().starts_with("User-Agent:")) { + if !headers.contains_key(reqwest::header::USER_AGENT) { headers.insert( reqwest::header::USER_AGENT, HeaderValue::from_str(DEFAULT_USER_AGENT) @@ -332,3 +332,42 @@ fn url_to_file_path(url: &Url) -> Result { fn url_to_file_path(url: &Url) -> Result { url.to_file_path() } + +#[cfg(test)] +mod tests { + use super::*; + use reqwest::header::HeaderMap; + + #[tokio::test] + async fn test_user_agent_header() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = Url::parse(&format!("http://{}", listener.local_addr().unwrap())).unwrap(); + + let http_handler = axum::routing::get(|actual_headers: HeaderMap| { + let user_agent = HeaderName::from_str("User-Agent").unwrap(); + assert_eq!(actual_headers[user_agent], HeaderValue::from_str("test-agent").unwrap()); + + async { "" } + }); + + let server_task = tokio::spawn(async move { + axum::serve(listener, http_handler.into_make_service()).await.unwrap() + }); + + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_headers(vec!["User-Agent: test-agent".to_string()]) + .build(); + let inner = transport.connect_http().await.unwrap(); + + match inner { + InnerTransport::Http(http) => { + let _ = http.client().get(url).send().await.unwrap(); + + // assert inside http_handler + } + _ => unreachable!(), + } + + server_task.abort(); + } +} diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index b79e095ceb..49eab352b7 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -6,6 +6,8 @@ use std::{future::Future, time::Duration}; /// Error type for Retry. #[derive(Debug, thiserror::Error)] pub enum RetryError { + /// Continues operation without decrementing retries. + Continue(E), /// Keeps retrying operation. Retry(E), /// Stops retrying operation immediately. @@ -74,6 +76,12 @@ impl Retry { { loop { match callback().await { + Err(RetryError::Continue(e)) => { + self.log(e, false); + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; + } + } Err(RetryError::Retry(e)) if self.retries > 0 => { self.handle_err(e); if !self.delay.is_zero() { @@ -89,9 +97,12 @@ impl Retry { fn handle_err(&mut self, err: Error) { debug_assert!(self.retries > 0); self.retries -= 1; - let _ = sh_warn!( - "{msg}{delay} ({retries} tries remaining)", - msg = crate::errors::display_chain(&err), + self.log(err, true); + } + + fn log(&self, err: Error, warn: bool) { + let msg = format!( + "{err}{delay} ({retries} tries remaining)", delay = if self.delay.is_zero() { String::new() } else { @@ -99,5 +110,10 @@ impl Retry { }, retries = self.retries, ); + if warn { + let _ = sh_warn!("{msg}"); + } else { + tracing::info!("{msg}"); + } } } diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index b9f7d6cd8a..33cd3fbb77 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -174,7 +174,11 @@ impl Reporter for SpinnerReporter { dirty_files .iter() .map(|path| { - let trimmed_path = path.strip_prefix(&project_root).unwrap_or(path); + let trimmed_path = if let Ok(project_root) = &project_root { + path.strip_prefix(project_root).unwrap_or(path) + } else { + path + }; format!("- {}", trimmed_path.display()) }) .sorted() diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 9148cd6d9a..1f2ea555bf 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -98,6 +98,45 @@ revertReason {}", } } +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 diff --git a/crates/common/src/version.rs b/crates/common/src/version.rs new file mode 100644 index 0000000000..f69457bf7c --- /dev/null +++ b/crates/common/src/version.rs @@ -0,0 +1,27 @@ +//! Foundry version information. + +/// The SemVer compatible version information for Foundry. +pub const SEMVER_VERSION: &str = env!("FOUNDRY_SEMVER_VERSION"); + +/// The short version message information for the Foundry CLI. +pub const SHORT_VERSION: &str = env!("FOUNDRY_SHORT_VERSION"); + +/// The long version message information for the Foundry CLI. +pub const LONG_VERSION: &str = concat!( + env!("FOUNDRY_LONG_VERSION_0"), + "\n", + env!("FOUNDRY_LONG_VERSION_1"), + "\n", + env!("FOUNDRY_LONG_VERSION_2"), + "\n", + env!("FOUNDRY_LONG_VERSION_3"), +); + +/// Whether the version is a nightly build. +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\ + To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment. \n"; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index bca5cbf578..1beb65c2c8 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -23,7 +23,7 @@ revm-primitives.workspace = true solar-parse.workspace = true -dirs-next = "2" +dirs.workspace = true dunce.workspace = true eyre.workspace = true figment = { workspace = true, features = ["toml", "env"] } diff --git a/crates/config/README.md b/crates/config/README.md index c452242bd7..0d3eafbcf0 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -86,7 +86,7 @@ gas_reports_ignore = [] # solc = '0.8.10' auto_detect_solc = true offline = false -optimizer = true +optimizer = false optimizer_runs = 200 model_checker = { contracts = { 'a.sol' = [ 'A1', diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 09f21605d1..eba84a57da 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -3,11 +3,9 @@ use alloy_primitives::map::HashSet; use figment::providers::{Format, Toml}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{error::Error, fmt, str::FromStr}; -/// The message shown upon panic if the config could not be extracted from the figment -pub const FAILED_TO_EXTRACT_CONFIG_PANIC_MSG: &str = "failed to extract foundry config:"; /// Represents a failed attempt to extract `Config` from a `Figment` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct ExtractConfigError { /// error thrown when extracting the `Config` pub(crate) error: figment::Error, @@ -40,7 +38,7 @@ impl fmt::Display for ExtractConfigError { unique_errors.push(err); } } - writeln!(f, "{FAILED_TO_EXTRACT_CONFIG_PANIC_MSG}")?; + writeln!(f, "failed to extract foundry config:")?; for err in unique_errors { writeln!(f, "{err}")?; } @@ -48,6 +46,12 @@ impl fmt::Display for ExtractConfigError { } } +impl fmt::Debug for ExtractConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + impl Error for ExtractConfigError { fn source(&self) -> Option<&(dyn Error + 'static)> { Error::source(&self.error) diff --git a/crates/config/src/fix.rs b/crates/config/src/fix.rs index f3ec1271fe..8181339a2d 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -230,7 +230,7 @@ mod tests { fn $name() { Jail::expect_with(|jail| { // setup home directory, - // **Note** this only has an effect on unix, as [`dirs_next::home_dir()`] on windows uses `FOLDERID_Profile` + // **Note** this only has an effect on unix, as [`dirs::home_dir()`] on windows uses `FOLDERID_Profile` jail.set_env("HOME", jail.directory().display().to_string()); std::fs::create_dir(jail.directory().join(".foundry")).unwrap(); @@ -302,7 +302,7 @@ mod tests { Ok(()) }); - // mocking the `$HOME` has no effect on windows, see [`dirs_next::home_dir()`] + // mocking the `$HOME` has no effect on windows, see [`dirs::home_dir()`] fix_test!( #[cfg(not(windows))] test_global_toml_is_edited, diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 5a6c02db9d..4f45d00bc9 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -79,6 +79,6 @@ impl InvariantConfig { self.failure_persist_dir .unwrap() .join("failures") - .join(contract_name.split(':').last().unwrap()) + .join(contract_name.split(':').next_back().unwrap()) } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 5628591f58..0aada03f35 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -229,8 +229,11 @@ pub struct Config { /// /// **Note** for backwards compatibility reasons this also accepts solc_version from the toml /// file, see `BackwardsCompatTomlProvider`. + /// + /// Avoid using this field directly; call the related `solc` methods instead. + #[doc(hidden)] pub solc: Option, - /// whether to autodetect the solc compiler version to use + /// Whether to autodetect the solc compiler version to use. pub auto_detect_solc: bool, /// Offline mode, if set, network access (downloading solc) is disallowed. /// @@ -240,7 +243,7 @@ pub struct Config { /// install it pub offline: bool, /// Whether to activate optimizer - pub optimizer: bool, + pub optimizer: Option, /// The number of runs specifies roughly how often each opcode of the deployed code will be /// executed across the life-time of the contract. This means it is a trade-off parameter /// between code size (deploy cost) and code execution cost (cost after deployment). @@ -251,7 +254,7 @@ pub struct Config { /// A common misconception is that this parameter specifies the number of iterations of the /// optimizer. This is not true: The optimizer will always run as many times as it can /// still improve the code. - pub optimizer_runs: usize, + pub optimizer_runs: Option, /// Switch optimizer components on or off in detail. /// The "enabled" switch above provides two defaults which can be /// tweaked here. If "details" is given, "enabled" can be omitted. @@ -320,6 +323,8 @@ pub struct Config { pub invariant: InvariantConfig, /// Whether to allow ffi cheatcodes in test pub ffi: 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. pub always_use_create_2_factory: bool, /// Sets a timeout in seconds for vm.prompt cheatcodes @@ -587,40 +592,33 @@ impl Config { /// Docker image with eof-enabled solc binary pub const EOF_SOLC_IMAGE: &'static str = "ghcr.io/paradigmxyz/forge-eof@sha256:46f868ce5264e1190881a3a335d41d7f42d6f26ed20b0c823609c715e38d603f"; - /// Returns the current `Config` + /// Loads the `Config` from the current directory. /// /// See [`figment`](Self::figment) for more details. - #[track_caller] - pub fn load() -> Self { + pub fn load() -> Result { Self::from_provider(Self::figment()) } - /// Returns the current `Config` with the given `providers` preset + /// Loads the `Config` with the given `providers` preset. /// /// See [`figment`](Self::figment) for more details. - #[track_caller] - pub fn load_with_providers(providers: FigmentProviders) -> Self { + pub fn load_with_providers(providers: FigmentProviders) -> Result { Self::from_provider(Self::default().to_figment(providers)) } - /// Returns the current `Config` + /// Loads the `Config` from the given root directory. /// /// See [`figment_with_root`](Self::figment_with_root) for more details. #[track_caller] - pub fn load_with_root(root: impl AsRef) -> Self { + pub fn load_with_root(root: impl AsRef) -> Result { Self::from_provider(Self::figment_with_root(root.as_ref())) } - /// Extract a `Config` from `provider`, panicking if extraction fails. - /// - /// # Panics - /// - /// If extraction fails, prints an error message indicating the failure and - /// panics. For a version that doesn't panic, use [`Config::try_from()`]. + /// Attempts to extract a `Config` from `provider`, returning the result. /// /// # Example /// - /// ```no_run + /// ```rust /// use figment::providers::{Env, Format, Toml}; /// use foundry_config::Config; /// @@ -630,31 +628,19 @@ impl Config { /// /// let config = Config::from_provider(figment); /// ``` - #[track_caller] - pub fn from_provider(provider: T) -> Self { + #[doc(alias = "try_from")] + pub fn from_provider(provider: T) -> Result { trace!("load config with provider: {:?}", provider.metadata()); - Self::try_from(provider).unwrap_or_else(|err| panic!("{}", err)) + Self::from_figment(Figment::from(provider)) } - /// Attempts to extract a `Config` from `provider`, returning the result. - /// - /// # Example - /// - /// ```rust - /// use figment::providers::{Env, Format, Toml}; - /// use foundry_config::Config; - /// - /// // Use foundry's default `Figment`, but allow values from `other.toml` - /// // to supersede its values. - /// let figment = Config::figment().merge(Toml::file("other.toml").nested()); - /// - /// let config = Config::try_from(figment); - /// ``` + #[doc(hidden)] + #[deprecated(note = "use `Config::from_provider` instead")] pub fn try_from(provider: T) -> Result { - Self::try_from_figment(Figment::from(provider)) + Self::from_provider(provider) } - fn try_from_figment(figment: Figment) -> Result { + fn from_figment(figment: Figment) -> Result { let mut config = figment.extract::().map_err(ExtractConfigError::new)?; config.profile = figment.profile().clone(); @@ -675,6 +661,8 @@ impl Config { add_profile(&Self::DEFAULT_PROFILE); add_profile(&config.profile); + config.normalize_optimizer_settings(); + Ok(config) } @@ -822,7 +810,7 @@ impl Config { self.fs_permissions.join_all(&root); - if let Some(ref mut model_checker) = self.model_checker { + if let Some(model_checker) = &mut self.model_checker { model_checker.contracts = std::mem::take(&mut model_checker.contracts) .into_iter() .map(|(path, contracts)| { @@ -840,17 +828,40 @@ impl Config { self } + /// Normalizes optimizer settings. + /// See + pub fn normalized_optimizer_settings(mut self) -> Self { + self.normalize_optimizer_settings(); + self + } + /// Normalizes the evm version if a [SolcReq] is set to a valid version. pub fn normalize_evm_version(&mut self) { self.evm_version = self.get_normalized_evm_version(); } - /// Returns the normalized [EvmVersion] if a [SolcReq] is set to a valid version or if the solc - /// path is a valid solc binary. - /// - /// Otherwise it returns the configured [EvmVersion]. + /// Normalizes optimizer settings: + /// - 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) { + match (self.optimizer, self.optimizer_runs) { + // Default: set the optimizer to false and optimizer runs to 200. + (None, None) => { + self.optimizer = Some(false); + self.optimizer_runs = Some(200); + } + // Set the optimizer runs to 200 if the `optimizer` config set. + (Some(_), None) => self.optimizer_runs = Some(200), + // Enables optimizer if the `optimizer_runs` has been set with a value greater than 0. + (None, Some(runs)) => self.optimizer = Some(runs > 0), + _ => {} + } + } + + /// 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.as_ref().and_then(|solc| solc.try_version().ok()) { + if let Some(version) = self.solc_version() { if let Some(evm_version) = self.evm_version.normalize_version_solc(&version) { return evm_version; } @@ -927,8 +938,9 @@ impl Config { /// /// ``` /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); - /// let project = config.project(); + /// let config = Config::load_with_root(".")?.sanitized(); + /// let project = config.project()?; + /// # Ok::<_, eyre::Error>(()) /// ``` pub fn project(&self) -> Result, SolcError> { self.create_project(self.cache, false) @@ -1065,12 +1077,6 @@ impl Config { remove_test_dir(&self.fuzz.failure_persist_dir); remove_test_dir(&self.invariant.failure_persist_dir); - // Remove snapshot directory. - let snapshot_dir = project.root().join(&self.snapshots); - if snapshot_dir.exists() { - let _ = fs::remove_dir_all(&snapshot_dir); - } - Ok(()) } @@ -1118,7 +1124,8 @@ impl Config { Err(RecvTimeoutError::Disconnected) => panic!("sender dropped"), }; } - if let Some(ref solc) = self.solc { + + if let Some(solc) = &self.solc { let solc = match solc { SolcReq::Version(version) => { if let Some(solc) = Solc::find_svm_installed_version(version)? { @@ -1182,8 +1189,9 @@ impl Config { /// ``` /// use foundry_compilers::solc::Solc; /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); + /// let config = Config::load_with_root(".")?.sanitized(); /// let paths = config.project_paths::(); + /// # Ok::<_, eyre::Error>(()) /// ``` pub fn project_paths(&self) -> ProjectPathsConfig { let mut builder = ProjectPathsConfig::builder() @@ -1215,6 +1223,11 @@ impl Config { } } + /// Returns the solc version, if any. + pub fn solc_version(&self) -> Option { + self.solc.as_ref().and_then(|solc| solc.try_version().ok()) + } + /// Returns configured [Vyper] compiler. pub fn vyper_compiler(&self) -> Result, SolcError> { // Only instantiate Vyper if there are any Vyper files in the project. @@ -1478,8 +1491,8 @@ impl Config { /// and pub fn optimizer(&self) -> Optimizer { Optimizer { - enabled: Some(self.optimizer), - runs: Some(self.optimizer_runs), + enabled: self.optimizer, + runs: self.optimizer_runs, // we always set the details because `enabled` is effectively a specific details profile // that can still be modified details: self.optimizer_details.clone(), @@ -1625,6 +1638,16 @@ impl Config { Self::with_root(root.as_ref()).into() } + #[doc(hidden)] + #[track_caller] + pub fn figment_with_root_opt(root: Option<&Path>) -> Figment { + let root = match root { + Some(root) => root, + None => &find_project_root(None).expect("could not determine project root"), + }; + Self::figment_with_root(root) + } + /// Creates a new Config that adds additional context extracted from the provided root. /// /// # Example @@ -1702,7 +1725,7 @@ impl Config { where F: FnOnce(&Self, &mut toml_edit::DocumentMut) -> bool, { - let config = Self::load_with_root(root).sanitized(); + let config = Self::load_with_root(root)?.sanitized(); config.update(|doc| f(&config, doc)) } @@ -1838,7 +1861,7 @@ impl Config { /// Returns the path to foundry's config dir: `~/.foundry/`. pub fn foundry_dir() -> Option { - dirs_next::home_dir().map(|p| p.join(Self::FOUNDRY_DIR_NAME)) + dirs::home_dir().map(|p| p.join(Self::FOUNDRY_DIR_NAME)) } /// Returns the path to foundry's cache dir: `~/.foundry/cache`. @@ -1891,7 +1914,7 @@ impl Config { /// | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | /// | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | pub fn data_dir() -> eyre::Result { - let path = dirs_next::data_dir().wrap_err("Failed to find data directory")?.join("foundry"); + let path = dirs::data_dir().wrap_err("Failed to find data directory")?.join("foundry"); std::fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; Ok(path) } @@ -2327,8 +2350,8 @@ impl Default for Config { vyper: Default::default(), auto_detect_solc: true, offline: false, - optimizer: true, - optimizer_runs: 200, + optimizer: None, + optimizer_runs: None, optimizer_details: None, model_checker: None, extra_output: Default::default(), @@ -2349,6 +2372,7 @@ impl Default for Config { invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, ffi: false, + allow_internal_expect_revert: false, prompt_timeout: 120, sender: Self::DEFAULT_SENDER, tx_origin: Self::DEFAULT_SENDER, @@ -2618,7 +2642,7 @@ mod tests { #[test] fn test_install_dir() { figment::Jail::expect_with(|jail| { - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); jail.create_file( "foundry.toml", @@ -2627,7 +2651,7 @@ mod tests { libs = ['node_modules', 'lib'] ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); jail.create_file( @@ -2637,7 +2661,7 @@ mod tests { libs = ['custom', 'node_modules', 'lib'] ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("custom")); Ok(()) @@ -2676,7 +2700,7 @@ mod tests { ", )?; - let config = crate::Config::load(); + let config = crate::Config::load().unwrap(); let expected: &[figment::Profile] = &["ci".into(), "default".into(), "local".into()]; assert_eq!(config.profiles, expected); @@ -2688,10 +2712,10 @@ mod tests { fn test_default_round_trip() { figment::Jail::expect_with(|_| { let original = Config::figment(); - let roundtrip = Figment::from(Config::from_provider(&original)); + let roundtrip = Figment::from(Config::from_provider(&original).unwrap()); for figment in &[original, roundtrip] { - let config = Config::from_provider(figment); - assert_eq!(config, Config::default()); + let config = Config::from_provider(figment).unwrap(); + assert_eq!(config, Config::default().normalized_optimizer_settings()); } Ok(()) }); @@ -2703,7 +2727,7 @@ mod tests { jail.set_env("FOUNDRY_FFI", "true"); jail.set_env("FFI", "true"); jail.set_env("DAPP_FFI", "true"); - let config = Config::load(); + let config = Config::load().unwrap(); assert!(!config.ffi); Ok(()) @@ -2731,7 +2755,7 @@ mod tests { ", )?; jail.set_env("FOUNDRY_PROFILE", "local"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("modules")]); Ok(()) @@ -2751,15 +2775,15 @@ mod tests { #[test] fn test_default_libs() { figment::Jail::expect_with(|jail| { - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("lib")]); fs::create_dir_all(jail.directory().join("node_modules")).unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("node_modules")]); fs::create_dir_all(jail.directory().join("lib")).unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); Ok(()) @@ -2782,12 +2806,12 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.src, PathBuf::from("defaultsrc")); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); jail.set_env("FOUNDRY_PROFILE", "custom"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.src, PathBuf::from("customsrc")); assert_eq!(config.test, PathBuf::from("defaulttest")); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); @@ -2807,7 +2831,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let paths_config = config.project_paths::(); assert_eq!(paths_config.tests, PathBuf::from(r"mytest")); Ok(()) @@ -2826,7 +2850,7 @@ mod tests { cache = true "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.remappings.is_empty()); jail.create_file( @@ -2837,7 +2861,7 @@ mod tests { ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, vec![ @@ -2847,7 +2871,7 @@ mod tests { ); jail.set_env("DAPP_REMAPPINGS", "ds-test=lib/ds-test/\nother/=lib/other/"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, @@ -2877,7 +2901,7 @@ mod tests { cache = true "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.remappings.is_empty()); jail.create_file( @@ -2888,7 +2912,7 @@ mod tests { ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, vec![ @@ -2898,7 +2922,7 @@ mod tests { ); jail.set_env("DAPP_REMAPPINGS", "ds-test/=lib/ds-test/src/\nenv-lib/=lib/env-lib/"); - let config = Config::load(); + let config = Config::load().unwrap(); // Remappings should now be: // - ds-test from environment (lib/ds-test/src/) @@ -2938,11 +2962,11 @@ mod tests { "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.libs.push("libs".into()); config.update_libs().unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("node_modules"), PathBuf::from("libs"),]); Ok(()) }); @@ -2962,8 +2986,14 @@ mod tests { ), )?; - let config = Config::load(); - assert_eq!(config, Config { gas_limit: gas.into(), ..Config::default() }); + let config = Config::load().unwrap(); + assert_eq!( + config, + Config { + gas_limit: gas.into(), + ..Config::default().normalized_optimizer_settings() + } + ); Ok(()) }); @@ -2981,7 +3011,7 @@ mod tests { "#, )?; - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); @@ -2993,7 +3023,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.set_env("FOUNDRY_CONFIG", "this config does not exist"); - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); @@ -3014,7 +3044,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) .is_err()); @@ -3061,7 +3091,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.etherscan.clone().resolved().has_unresolved()); @@ -3114,7 +3144,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let etherscan = config.get_etherscan_config().unwrap().unwrap(); assert_eq!(etherscan.chain, Some(NamedChain::Sepolia.into())); assert_eq!(etherscan.key, "FX42Z3BBJJEWXWGYV2X1CIPRSCN"); @@ -3137,7 +3167,7 @@ mod tests { )?; jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); - let mut config = Config::load(); + let mut config = Config::load().unwrap(); assert_eq!("http://localhost:8545", config.get_rpc_url_or_localhost_http().unwrap()); config.eth_rpc_url = Some("mainnet".to_string()); @@ -3166,7 +3196,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!("http://localhost:8545", config.get_rpc_url_or_localhost_http().unwrap()); Ok(()) @@ -3184,13 +3214,13 @@ mod tests { polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.eth_rpc_url = Some("polygonMumbai".to_string()); assert!(config.get_rpc_url().unwrap().is_err()); jail.set_env("_RESOLVE_RPC_ALIAS", "123455"); - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.eth_rpc_url = Some("polygonMumbai".to_string()); assert_eq!( "https://polygon-mumbai.g.alchemy.com/v2/123455", @@ -3218,7 +3248,7 @@ mod tests { jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARB_ONE", "123455"); jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARBISCAN", "123455"); - let config = Config::load(); + let config = Config::load().unwrap(); let config = config.get_etherscan_config_with_chain(Some(NamedChain::Arbitrum.into())); assert!(config.is_err()); @@ -3241,7 +3271,7 @@ mod tests { )?; jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( RpcEndpoints::new([ ( @@ -3309,7 +3339,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); jail.set_env("_CONFIG_AUTH", "123456"); jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); @@ -3385,7 +3415,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.get_rpc_url().unwrap().unwrap(), "https://example.com/"); @@ -3444,7 +3474,7 @@ mod tests { "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); let optimism = config.get_etherscan_api_key(Some(NamedChain::Optimism.into())); assert_eq!(optimism, Some("https://etherscan-optimism.com/".to_string())); @@ -3471,7 +3501,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let mumbai = config .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) @@ -3496,7 +3526,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let mumbai = config .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) @@ -3526,7 +3556,7 @@ mod tests { "#, )?; - let config = Config::load(); + 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()); @@ -3568,7 +3598,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { @@ -3611,7 +3641,7 @@ mod tests { ]), build_info_path: Some("build-info".into()), always_use_create_2_factory: true, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); @@ -3630,7 +3660,7 @@ mod tests { ", )?; - let config = Config::load_with_root(jail.directory()); + let config = Config::load_with_root(jail.directory()).unwrap(); assert_eq!( config.remappings, vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] @@ -3715,7 +3745,7 @@ mod tests { "#, )?; - let config = Config::load_with_root(jail.directory()); + let config = Config::load_with_root(jail.directory()).unwrap(); assert_eq!(config.ignored_file_paths, vec![PathBuf::from("something")]); assert_eq!(config.fuzz.seed, Some(U256::from(1000))); @@ -3753,7 +3783,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( @@ -3764,7 +3794,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( @@ -3775,11 +3805,11 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Local("path/to/local/solc".into()))); jail.set_env("FOUNDRY_SOLC_VERSION", "0.6.6"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 6, 6)))); Ok(()) }); @@ -3798,7 +3828,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); Ok(()) @@ -3813,7 +3843,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 20)))); Ok(()) @@ -3836,7 +3866,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { @@ -3846,7 +3876,7 @@ mod tests { eth_rpc_url: Some("https://example.com/".to_string()), auto_detect_solc: false, evm_version: EvmVersion::Berlin, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); @@ -3866,7 +3896,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.extra_output, @@ -3891,26 +3921,26 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { src: "mysrc".into(), out: "myout".into(), verbosity: 3, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); jail.set_env("FOUNDRY_SRC", r"other-src"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { src: "other-src".into(), out: "myout".into(), verbosity: 3, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); @@ -3938,7 +3968,7 @@ mod tests { src = "other-src" "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!(loaded.evm_version, EvmVersion::Berlin); let base = loaded.into_basic(); let default = Config::default(); @@ -3979,7 +4009,7 @@ mod tests { dictionary_weight = 101 ", )?; - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); } @@ -4007,7 +4037,7 @@ mod tests { )?; let invariant_default = InvariantConfig::default(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_ne!(config.invariant.runs, config.fuzz.runs); assert_eq!(config.invariant.runs, 420); @@ -4031,7 +4061,7 @@ mod tests { ); jail.set_env("FOUNDRY_PROFILE", "ci"); - let ci_config = Config::load(); + let ci_config = Config::load().unwrap(); assert_eq!(ci_config.fuzz.runs, 1); assert_eq!(ci_config.invariant.runs, 400); assert_eq!(ci_config.fuzz.dictionary.dictionary_weight, 5); @@ -4064,12 +4094,12 @@ mod tests { ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fuzz.runs, 100); assert_eq!(config.invariant.runs, 120); jail.set_env("FOUNDRY_PROFILE", "ci"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fuzz.runs, 420); assert_eq!(config.invariant.runs, 500); @@ -4089,15 +4119,15 @@ mod tests { jail.set_env("DAPP_BUILD_OPTIMIZE_RUNS", 999); jail.set_env("DAPP_BUILD_OPTIMIZE", 0); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.block_number, 1337); assert_eq!(config.sender, addr); assert_eq!(config.fuzz.runs, 420); assert_eq!(config.invariant.depth, 20); assert_eq!(config.fork_block_number, Some(100)); - assert_eq!(config.optimizer_runs, 999); - assert!(!config.optimizer); + assert_eq!(config.optimizer_runs, Some(999)); + assert!(!config.optimizer.unwrap()); Ok(()) }); @@ -4110,7 +4140,7 @@ mod tests { "DAPP_LIBRARIES", "[src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6]", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" @@ -4121,7 +4151,7 @@ mod tests { "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" @@ -4132,7 +4162,7 @@ mod tests { "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6,src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec![ @@ -4163,7 +4193,7 @@ mod tests { ] ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); let libs = config.parsed_libraries().unwrap().libs; @@ -4213,11 +4243,11 @@ mod tests { #[test] fn config_roundtrip() { figment::Jail::expect_with(|jail| { - let default = Config::default(); + let default = Config::default().normalized_optimizer_settings(); let basic = default.clone().into_basic(); jail.create_file("foundry.toml", &basic.to_string_pretty().unwrap())?; - let mut other = Config::load(); + let mut other = Config::load().unwrap(); clear_warning(&mut other); assert_eq!(default, other); @@ -4225,7 +4255,7 @@ mod tests { assert_eq!(basic, other); jail.create_file("foundry.toml", &default.to_string_pretty().unwrap())?; - let mut other = Config::load(); + let mut other = Config::load().unwrap(); clear_warning(&mut other); assert_eq!(default, other); @@ -4243,7 +4273,7 @@ mod tests { fs_permissions = [{ access = "read-write", path = "./"}] "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!( loaded.fs_permissions, @@ -4257,7 +4287,7 @@ mod tests { fs_permissions = [{ access = "none", path = "./"}] "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!(loaded.fs_permissions, FsPermissions::new(vec![PathPermission::none("./")])); Ok(()) @@ -4280,7 +4310,7 @@ mod tests { stackAllocation = true ", )?; - let mut loaded = Config::load(); + let mut loaded = Config::load().unwrap(); clear_warning(&mut loaded); assert_eq!( loaded.optimizer_details, @@ -4297,7 +4327,7 @@ mod tests { let s = loaded.to_string_pretty().unwrap(); jail.create_file("foundry.toml", &s)?; - let mut reloaded = Config::load(); + let mut reloaded = Config::load().unwrap(); clear_warning(&mut reloaded); assert_eq!(loaded, reloaded); @@ -4320,7 +4350,7 @@ mod tests { timeout = 10000 ", )?; - let mut loaded = Config::load(); + let mut loaded = Config::load().unwrap(); clear_warning(&mut loaded); assert_eq!( loaded.model_checker, @@ -4347,7 +4377,7 @@ mod tests { let s = loaded.to_string_pretty().unwrap(); jail.create_file("foundry.toml", &s)?; - let mut reloaded = Config::load(); + let mut reloaded = Config::load().unwrap(); clear_warning(&mut reloaded); assert_eq!(loaded, reloaded); @@ -4370,7 +4400,7 @@ mod tests { timeout = 10000 ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); // NOTE(onbjerg): We have to canonicalize the path here using dunce because figment will // canonicalize the jail path using the standard library. The standard library *always* @@ -4422,7 +4452,7 @@ mod tests { bracket_spacing = true ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!( loaded.fmt, FormatterConfig { @@ -4449,7 +4479,7 @@ mod tests { ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!( loaded.invariant, InvariantConfig { @@ -4482,7 +4512,7 @@ mod tests { jail.set_env("FOUNDRY_FUZZ_DICTIONARY_WEIGHT", "99"); jail.set_env("FOUNDRY_INVARIANT_DEPTH", "5"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fmt.line_length, 95); assert_eq!(config.fuzz.dictionary.dictionary_weight, 99); assert_eq!(config.invariant.depth, 5); @@ -4527,7 +4557,7 @@ mod tests { out = 'my-out' ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!(loaded.src.file_name().unwrap(), "my-src"); assert_eq!(loaded.out.file_name().unwrap(), "my-out"); assert_eq!( @@ -4552,11 +4582,11 @@ mod tests { ", )?; jail.set_env("ETHERSCAN_API_KEY", ""); - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert!(loaded.etherscan_api_key.is_none()); jail.set_env("ETHERSCAN_API_KEY", "DUMMY"); - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!(loaded.etherscan_api_key, Some("DUMMY".into())); Ok(()) @@ -4578,7 +4608,7 @@ mod tests { let figment = Config::figment_with_root(jail.directory()) .merge(("etherscan_api_key", "USER_KEY")); - let loaded = Config::from_provider(figment); + let loaded = Config::from_provider(figment).unwrap(); assert_eq!(loaded.etherscan_api_key, Some("USER_KEY".into())); Ok(()) @@ -4596,7 +4626,7 @@ mod tests { ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!(loaded.evm_version, EvmVersion::London); Ok(()) }); @@ -4652,7 +4682,6 @@ mod tests { } let _figment: Figment = From::from(&MyArgs::default()); - let _config: Config = From::from(&MyArgs::default()); #[derive(Default)] struct Outer { @@ -4663,7 +4692,6 @@ mod tests { impl_figment_convert!(Outer, start, other, another); let _figment: Figment = From::from(&Outer::default()); - let _config: Config = From::from(&Outer::default()); } #[test] @@ -4756,7 +4784,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.ignored_error_codes, vec![ @@ -4781,7 +4809,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.ignored_file_paths, vec![Path::new("something").to_path_buf()]); Ok(()) @@ -4799,7 +4827,7 @@ mod tests { ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.optimizer_details, Some(OptimizerDetails::default())); Ok(()) @@ -4818,7 +4846,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.labels, AddressHashMap::from_iter(vec![ @@ -4850,7 +4878,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.vyper, VyperConfig { @@ -4880,7 +4908,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.soldeer, diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index b9261f0345..cb5dc9771a 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -40,7 +40,6 @@ /// } /// /// let figment: Figment = From::from(&MyArgs::default()); -/// let config: Config = From::from(&MyArgs::default()); /// /// // Use `impl_figment` on a type that has several nested `Provider` as fields but is _not_ a `Provider` itself /// @@ -53,23 +52,13 @@ /// impl_figment_convert!(Outer, start, second, third); /// /// let figment: Figment = From::from(&Outer::default()); -/// let config: Config = From::from(&Outer::default()); /// ``` #[macro_export] macro_rules! impl_figment_convert { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - let root = args.root.clone() - .unwrap_or_else(|| $crate::find_project_root(None)); - $crate::Config::figment_with_root(&root).merge(args) - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() + $crate::Config::figment_with_root_opt(args.root.as_deref()).merge(args) } } }; @@ -83,13 +72,6 @@ macro_rules! impl_figment_convert { figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; ($name:ty, self, $start:ident $(, $more:ident)*) => { impl<'a> From<&'a $name> for $crate::figment::Figment { @@ -102,13 +84,6 @@ macro_rules! impl_figment_convert { figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } @@ -173,13 +148,6 @@ macro_rules! merge_impl_figment_convert { figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } @@ -193,18 +161,13 @@ macro_rules! impl_figment_convert_cast { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - $crate::Config::with_root(&$crate::find_project_root(None)) + let root = + $crate::find_project_root(None).expect("could not determine project root"); + $crate::Config::with_root(&root) .to_figment($crate::FigmentProviders::Cast) .merge(args) } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index c941ed2c91..c6eb55fe99 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -69,16 +69,37 @@ impl Remappings { } /// Push an element to the remappings vector, but only if it's not already present. - pub fn push(&mut self, remapping: Remapping) { + fn push(&mut self, remapping: Remapping) { + // Special handling for .sol file remappings, only allow one remapping per source file. + if remapping.name.ends_with(".sol") && !remapping.path.ends_with(".sol") { + return; + } + 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 + } + // What we're doing here is filtering for ambiguous paths. For example, if we have - // @prb/=node_modules/@prb/ as existing, and - // @prb/math/=node_modules/@prb/math/src/ as the one being checked, + // @prb/math/=node_modules/@prb/math/src/ as existing, and + // @prb/=node_modules/@prb/ as the one being checked, // we want to keep the already existing one, which is the first one. This way we avoid // having to deal with ambiguous paths which is unwanted when autodetecting remappings. // Remappings are added from root of the project down to libraries, so - // we want to exclude any conflicting remappings added from libraries. - remapping.name.starts_with(&existing.name) && existing.context == remapping.context + // we also want to exclude any conflicting remappings added from libraries. For example, + // if we have `@utils/=src/` added in project remappings and `@utils/libraries/=src/` + // added in a dependency, we don't want to add the new one as it conflicts with project + // existing remapping. + let mut existing_name_path = existing.name.clone(); + 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); + is_conflicting && existing.context == remapping.context }) { return; }; @@ -240,7 +261,8 @@ impl RemappingsProvider<'_> { }) .flat_map(|lib: PathBuf| { // load config, of the nested lib if it exists - let config = Config::load_with_root(&lib).sanitized(); + 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, @@ -310,3 +332,132 @@ impl Provider for RemappingsProvider<'_> { Some(Config::selected_profile()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sol_file_remappings() { + let mut remappings = Remappings::new(); + + // First valid remapping + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Same source to different target (should be rejected) + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract2.sol".to_string(), + }); + + // Different source to same target (should be allowed) + remappings.push(Remapping { + context: None, + name: "OtherContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Exact duplicate (should be silently ignored) + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Invalid .sol remapping (target not .sol) + remappings.push(Remapping { + context: None, + name: "Invalid.sol".to_string(), + path: "implementations/Contract1.txt".to_string(), + }); + + let result = remappings.into_inner(); + assert_eq!(result.len(), 2, "Should only have 2 valid remappings"); + + // Verify the correct remappings exist + assert!( + result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract1.sol"), + "Should keep first mapping of MyContract.sol" + ); + assert!( + !result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract2.sol"), + "Should keep first mapping of MyContract.sol" + ); + assert!(result.iter().any(|r| r.name == "OtherContract.sol" && r.path == "implementations/Contract1.sol"), + "Should allow different source to same target"); + + // Verify the rejected remapping doesn't exist + assert!( + !result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract2.sol"), + "Should reject same source to different target" + ); + } + + #[test] + fn test_mixed_remappings() { + let mut remappings = Remappings::new(); + + remappings.push(Remapping { + context: None, + name: "@openzeppelin-contracts/".to_string(), + path: "lib/openzeppelin-contracts/".to_string(), + }); + remappings.push(Remapping { + context: None, + name: "@openzeppelin/contracts/".to_string(), + path: "lib/openzeppelin/contracts/".to_string(), + }); + + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "os/Contract.sol".to_string(), + }); + + let result = remappings.into_inner(); + assert_eq!(result.len(), 3, "Should have 3 remappings"); + assert_eq!(result.first().unwrap().name, "@openzeppelin-contracts/"); + assert_eq!(result.first().unwrap().path, "lib/openzeppelin-contracts/"); + assert_eq!(result.get(1).unwrap().name, "@openzeppelin/contracts/"); + assert_eq!(result.get(1).unwrap().path, "lib/openzeppelin/contracts/"); + assert_eq!(result.get(2).unwrap().name, "MyContract.sol"); + assert_eq!(result.get(2).unwrap().path, "os/Contract.sol"); + } + + #[test] + fn test_remappings_with_context() { + let mut remappings = Remappings::new(); + + // Same name but different contexts + remappings.push(Remapping { + context: Some("test/".to_string()), + name: "MyContract.sol".to_string(), + path: "test/Contract.sol".to_string(), + }); + remappings.push(Remapping { + context: Some("prod/".to_string()), + name: "MyContract.sol".to_string(), + path: "prod/Contract.sol".to_string(), + }); + + 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")); + } +} diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 43b9b74689..94f4823dc7 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -15,23 +15,20 @@ use std::{ str::FromStr, }; -/// Loads the config for the current project workspace -pub fn load_config() -> Config { +// TODO: Why do these exist separately from `Config::load`? + +/// Loads the config for the current project workspace. +pub fn load_config() -> eyre::Result { load_config_with_root(None) } /// Loads the config for the current project workspace or the provided root path. -/// -/// # Panics -/// -/// Panics if the project root cannot be found. See [`find_project_root`]. -#[track_caller] -pub fn load_config_with_root(root: Option<&Path>) -> Config { +pub fn load_config_with_root(root: Option<&Path>) -> eyre::Result { let root = match root { Some(root) => root, - None => &find_project_root(None), + None => &find_project_root(None)?, }; - Config::load_with_root(root).sanitized() + Ok(Config::load_with_root(root)?.sanitized()) } /// Returns the path of the top-level directory of the working git tree. @@ -59,20 +56,10 @@ pub fn find_git_root(relative_to: &Path) -> io::Result> { /// /// Returns `repo` or `cwd` if no `foundry.toml` is found in the tree. /// -/// # Panics -/// -/// Panics if: +/// Returns an error if: /// - `cwd` is `Some` and is not a valid directory; /// - `cwd` is `None` and the [`std::env::current_dir`] call fails. -#[track_caller] -pub fn find_project_root(cwd: Option<&Path>) -> PathBuf { - try_find_project_root(cwd).expect("Could not find project root") -} - -/// Returns the root path to set for the project root. -/// -/// Same as [`find_project_root`], but returns an error instead of panicking. -pub fn try_find_project_root(cwd: Option<&Path>) -> io::Result { +pub fn find_project_root(cwd: Option<&Path>) -> io::Result { let cwd = match cwd { Some(path) => path, None => &std::env::current_dir()?, diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs index 469225118e..907232cad7 100644 --- a/crates/debugger/src/debugger.rs +++ b/crates/debugger/src/debugger.rs @@ -1,11 +1,11 @@ //! Debugger implementation. -use crate::{tui::TUI, DebugNode, DebuggerBuilder, ExitReason, FileDumper}; +use crate::{tui::TUI, DebugNode, DebuggerBuilder, ExitReason}; use alloy_primitives::map::AddressHashMap; use eyre::Result; use foundry_common::evm::Breakpoints; use foundry_evm_traces::debug::ContractSources; -use std::path::PathBuf; +use std::path::Path; pub struct DebuggerContext { pub debug_arena: Vec, @@ -64,10 +64,8 @@ impl Debugger { } /// Dumps debugger data to file. - pub fn dump_to_file(&mut self, path: &PathBuf) -> Result<()> { + pub fn dump_to_file(&mut self, path: &Path) -> Result<()> { eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty"); - - let mut file_dumper = FileDumper::new(path, &mut self.context); - file_dumper.run() + crate::dump::dump(path, &self.context) } } diff --git a/crates/debugger/src/dump.rs b/crates/debugger/src/dump.rs new file mode 100644 index 0000000000..83af7b0e77 --- /dev/null +++ b/crates/debugger/src/dump.rs @@ -0,0 +1,148 @@ +use crate::{debugger::DebuggerContext, DebugNode}; +use alloy_primitives::map::AddressMap; +use foundry_common::fs::write_json_file; +use foundry_compilers::{ + artifacts::sourcemap::{Jump, SourceElement}, + multi::MultiCompilerLanguage, +}; +use foundry_evm_core::utils::PcIcMap; +use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; +use serde::Serialize; +use std::{collections::HashMap, path::Path}; + +/// Dumps debugger data to a JSON file. +pub(crate) fn dump(path: &Path, context: &DebuggerContext) -> eyre::Result<()> { + write_json_file(path, &DebuggerDump::new(context))?; + Ok(()) +} + +/// Holds info of debugger dump. +#[derive(Serialize)] +struct DebuggerDump<'a> { + contracts: ContractsDump<'a>, + debug_arena: &'a [DebugNode], +} + +impl<'a> DebuggerDump<'a> { + fn new(debugger_context: &'a DebuggerContext) -> Self { + Self { + contracts: ContractsDump::new(debugger_context), + debug_arena: &debugger_context.debug_arena, + } + } +} + +#[derive(Serialize)] +struct SourceElementDump { + offset: u32, + length: u32, + index: i32, + jump: u32, + modifier_depth: u32, +} + +impl SourceElementDump { + fn new(v: &SourceElement) -> Self { + Self { + offset: v.offset(), + length: v.length(), + index: v.index_i32(), + jump: match v.jump() { + Jump::In => 0, + Jump::Out => 1, + Jump::Regular => 2, + }, + modifier_depth: v.modifier_depth(), + } + } +} + +#[derive(Serialize)] +struct ContractsDump<'a> { + identified_contracts: &'a AddressMap, + sources: ContractsSourcesDump<'a>, +} + +impl<'a> ContractsDump<'a> { + fn new(debugger_context: &'a DebuggerContext) -> Self { + Self { + identified_contracts: &debugger_context.identified_contracts, + sources: ContractsSourcesDump::new(&debugger_context.contracts_sources), + } + } +} + +#[derive(Serialize)] +struct ContractsSourcesDump<'a> { + sources_by_id: HashMap<&'a str, HashMap>>, + artifacts_by_name: HashMap<&'a str, Vec>>, +} + +impl<'a> ContractsSourcesDump<'a> { + fn new(contracts_sources: &'a ContractSources) -> Self { + Self { + sources_by_id: contracts_sources + .sources_by_id + .iter() + .map(|(name, inner_map)| { + ( + name.as_str(), + inner_map + .iter() + .map(|(id, source_data)| (*id, SourceDataDump::new(source_data))) + .collect(), + ) + }) + .collect(), + artifacts_by_name: contracts_sources + .artifacts_by_name + .iter() + .map(|(name, data)| { + (name.as_str(), data.iter().map(ArtifactDataDump::new).collect()) + }) + .collect(), + } + } +} + +#[derive(Serialize)] +struct SourceDataDump<'a> { + source: &'a str, + language: MultiCompilerLanguage, + path: &'a Path, +} + +impl<'a> SourceDataDump<'a> { + fn new(v: &'a SourceData) -> Self { + Self { source: &v.source, language: v.language, path: &v.path } + } +} + +#[derive(Serialize)] +struct ArtifactDataDump<'a> { + source_map: Option>, + source_map_runtime: Option>, + pc_ic_map: Option<&'a PcIcMap>, + pc_ic_map_runtime: Option<&'a PcIcMap>, + build_id: &'a str, + file_id: u32, +} + +impl<'a> ArtifactDataDump<'a> { + fn new(v: &'a ArtifactData) -> Self { + Self { + source_map: v + .source_map + .as_ref() + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + source_map_runtime: v + .source_map_runtime + .as_ref() + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + pc_ic_map: v.pc_ic_map.as_ref(), + pc_ic_map_runtime: v.pc_ic_map_runtime.as_ref(), + build_id: &v.build_id, + file_id: v.file_id, + } + } +} diff --git a/crates/debugger/src/file_dumper.rs b/crates/debugger/src/file_dumper.rs deleted file mode 100644 index 909530c421..0000000000 --- a/crates/debugger/src/file_dumper.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! The debug file dumper implementation. - -use crate::{debugger::DebuggerContext, DebugNode}; -use alloy_primitives::Address; -use eyre::Result; -use foundry_common::fs::write_json_file; -use foundry_compilers::{ - artifacts::sourcemap::{Jump, SourceElement}, - multi::MultiCompilerLanguage, -}; -use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; -use serde::Serialize; -use std::{collections::HashMap, ops::Deref, path::PathBuf}; - -/// Generates and writes debugger dump in a json file. -pub struct FileDumper<'a> { - /// Path to json file to write dump into. - path: &'a PathBuf, - /// Debugger context to generate dump for. - debugger_context: &'a mut DebuggerContext, -} - -impl<'a> FileDumper<'a> { - pub fn new(path: &'a PathBuf, debugger_context: &'a mut DebuggerContext) -> Self { - Self { path, debugger_context } - } - - pub fn run(&mut self) -> Result<()> { - let data = DebuggerDump::from(self.debugger_context); - write_json_file(self.path, &data).unwrap(); - Ok(()) - } -} - -/// Holds info of debugger dump. -#[derive(Serialize)] -struct DebuggerDump { - contracts: ContractsDump, - debug_arena: Vec, -} - -impl DebuggerDump { - fn from(debugger_context: &DebuggerContext) -> Self { - Self { - contracts: ContractsDump::new(debugger_context), - debug_arena: debugger_context.debug_arena.clone(), - } - } -} - -#[derive(Serialize)] -pub struct SourceElementDump { - offset: u32, - length: u32, - index: i32, - jump: u32, - modifier_depth: u32, -} - -impl SourceElementDump { - pub fn new(v: &SourceElement) -> Self { - Self { - offset: v.offset(), - length: v.length(), - index: v.index_i32(), - jump: match v.jump() { - Jump::In => 0, - Jump::Out => 1, - Jump::Regular => 2, - }, - modifier_depth: v.modifier_depth(), - } - } -} - -#[derive(Serialize)] -struct ContractsDump { - // Map of call address to contract name - identified_contracts: HashMap, - sources: ContractsSourcesDump, -} - -impl ContractsDump { - pub fn new(debugger_context: &DebuggerContext) -> Self { - Self { - identified_contracts: debugger_context - .identified_contracts - .iter() - .map(|(k, v)| (*k, v.clone())) - .collect(), - sources: ContractsSourcesDump::new(&debugger_context.contracts_sources), - } - } -} - -#[derive(Serialize)] -struct ContractsSourcesDump { - sources_by_id: HashMap>, - artifacts_by_name: HashMap>, -} - -impl ContractsSourcesDump { - pub fn new(contracts_sources: &ContractSources) -> Self { - Self { - sources_by_id: contracts_sources - .sources_by_id - .iter() - .map(|(name, inner_map)| { - ( - name.clone(), - inner_map - .iter() - .map(|(id, source_data)| (*id, SourceDataDump::new(source_data))) - .collect(), - ) - }) - .collect(), - artifacts_by_name: contracts_sources - .artifacts_by_name - .iter() - .map(|(name, data)| { - (name.clone(), data.iter().map(ArtifactDataDump::new).collect()) - }) - .collect(), - } - } -} - -#[derive(Serialize)] -struct SourceDataDump { - source: String, - language: MultiCompilerLanguage, - path: PathBuf, -} - -impl SourceDataDump { - pub fn new(v: &SourceData) -> Self { - Self { source: v.source.deref().clone(), language: v.language, path: v.path.clone() } - } -} - -#[derive(Serialize)] -struct ArtifactDataDump { - pub source_map: Option>, - pub source_map_runtime: Option>, - pub pc_ic_map: Option>, - pub pc_ic_map_runtime: Option>, - pub build_id: String, - pub file_id: u32, -} - -impl ArtifactDataDump { - pub fn new(v: &ArtifactData) -> Self { - Self { - source_map: v - .source_map - .clone() - .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), - source_map_runtime: v - .source_map_runtime - .clone() - .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), - pc_ic_map: v.pc_ic_map.clone().map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), - pc_ic_map_runtime: v - .pc_ic_map_runtime - .clone() - .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), - build_id: v.build_id.clone(), - file_id: v.file_id, - } - } -} diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index 67c9ee9845..1c1bf9614e 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -15,7 +15,7 @@ mod op; mod builder; mod debugger; -mod file_dumper; +mod dump; mod tui; mod node; @@ -24,5 +24,4 @@ pub use node::DebugNode; pub use builder::DebuggerBuilder; pub use debugger::Debugger; -pub use file_dumper::FileDumper; pub use tui::{ExitReason, TUI}; diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 18b5892796..a918bc89ea 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -351,7 +351,7 @@ impl TUIContext<'_> { .contracts_sources .find_source_mapping( contract_name, - self.current_step().pc, + self.current_step().pc as u32, self.debug_call().kind.is_any_create(), ) .ok_or_else(|| format!("No source map for contract {contract_name}")) diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 02a23ace5b..6c2bec99d1 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -389,7 +389,7 @@ impl DocBuilder { } if let Some(path) = base_path { - let title = path.iter().last().unwrap().to_string_lossy(); + let title = path.iter().next_back().unwrap().to_string_lossy(); if depth == 1 { summary.write_title(&title)?; } else { @@ -444,7 +444,7 @@ impl DocBuilder { readme.write_link_list_item(ident, &readme_path.display().to_string(), 0)?; } } else { - let name = path.iter().last().unwrap().to_string_lossy(); + let name = path.iter().next_back().unwrap().to_string_lossy(); let readme_path = Path::new("/").join(&path).display().to_string(); readme.write_link_list_item(&name, &readme_path, 0)?; self.write_summary_section(summary, &files, Some(&path), depth + 1)?; diff --git a/crates/evm/abi/src/HardhatConsole.json b/crates/evm/abi/src/Console.json similarity index 100% rename from crates/evm/abi/src/HardhatConsole.json rename to crates/evm/abi/src/Console.json diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py index e0ca8aa899..2e28fece21 100755 --- a/crates/evm/abi/src/console.py +++ b/crates/evm/abi/src/console.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# Generates the JSON ABI for console.sol. import json import re @@ -7,12 +8,10 @@ def main(): - if len(sys.argv) < 4: - print( - f"Usage: {sys.argv[0]} " - ) + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ") sys.exit(1) - [console_file, abi_file, patches_file] = sys.argv[1:4] + [console_file, abi_file] = sys.argv[1:3] # Parse signatures from `console.sol`'s string literals console_sol = open(console_file).read() @@ -41,37 +40,6 @@ def main(): abi = combined["contracts"][":HardhatConsole"]["abi"] open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None)) - # Make patches - patches = [] - for raw_sig in raw_sigs: - patched = raw_sig.replace("int", "int256") - if raw_sig != patched: - patches.append([raw_sig, patched]) - - # Generate the Rust patches map - codegen = "[\n" - for [original, patched] in patches: - codegen += f" // `{original}` -> `{patched}`\n" - - original_selector = selector(original) - patched_selector = selector(patched) - codegen += f" // `{original_selector.hex()}` -> `{patched_selector.hex()}`\n" - - codegen += ( - f" ({list(iter(original_selector))}, {list(iter(patched_selector))}),\n" - ) - codegen += "]\n" - open(patches_file, "w").write(codegen) - - -def keccak256(s): - r = subprocess.run(["cast", "keccak256", s], capture_output=True) - return bytes.fromhex(r.stdout.decode("utf8").strip()[2:]) - - -def selector(s): - return keccak256(s)[:4] - if __name__ == "__main__": main() diff --git a/crates/evm/abi/src/console/ds.rs b/crates/evm/abi/src/console/ds.rs new file mode 100644 index 0000000000..444be0d77d --- /dev/null +++ b/crates/evm/abi/src/console/ds.rs @@ -0,0 +1,83 @@ +//! DSTest log interface. + +use super::{format_units_int, format_units_uint}; +use alloy_primitives::hex; +use alloy_sol_types::sol; +use derive_more::Display; +use itertools::Itertools; + +// TODO: Use `UiFmt` + +sol! { +#[sol(abi)] +#[derive(Display)] +interface Console { + #[display("{val}")] + event log(string val); + + #[display("{}", hex::encode_prefixed(val))] + event logs(bytes val); + + #[display("{val}")] + event log_address(address val); + + #[display("{val}")] + event log_bytes32(bytes32 val); + + #[display("{val}")] + event log_int(int val); + + #[display("{val}")] + event log_uint(uint val); + + #[display("{}", hex::encode_prefixed(val))] + event log_bytes(bytes val); + + #[display("{val}")] + event log_string(string val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(uint256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(int256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(address[] val); + + #[display("{key}: {val}")] + event log_named_address(string key, address val); + + #[display("{key}: {val}")] + event log_named_bytes32(string key, bytes32 val); + + #[display("{key}: {}", format_units_int(val, decimals))] + event log_named_decimal_int(string key, int val, uint decimals); + + #[display("{key}: {}", format_units_uint(val, decimals))] + event log_named_decimal_uint(string key, uint val, uint decimals); + + #[display("{key}: {val}")] + event log_named_int(string key, int val); + + #[display("{key}: {val}")] + event log_named_uint(string key, uint val); + + #[display("{key}: {}", hex::encode_prefixed(val))] + event log_named_bytes(string key, bytes val); + + #[display("{key}: {val}")] + event log_named_string(string key, string val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, uint256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, int256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, address[] val); +} +} + +pub use Console::*; diff --git a/crates/evm/abi/src/console/hardhat.rs b/crates/evm/abi/src/console/hardhat.rs deleted file mode 100644 index 4081982e31..0000000000 --- a/crates/evm/abi/src/console/hardhat.rs +++ /dev/null @@ -1,61 +0,0 @@ -use alloy_primitives::{address, map::HashMap, Address, Selector}; -use alloy_sol_types::sol; -use foundry_common_fmt::*; -use foundry_macros::ConsoleFmt; -use std::sync::LazyLock; - -sol!( - #[sol(abi)] - #[derive(ConsoleFmt)] - HardhatConsole, - "src/HardhatConsole.json" -); - -/// The Hardhat console address. -/// -/// See: -pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); - -/// Patches the given Hardhat `console` function selector to its ABI-normalized form. -/// -/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. -pub fn patch_hh_console_selector(input: &mut [u8]) { - if let Some(selector) = hh_console_selector(input) { - input[..4].copy_from_slice(selector.as_slice()); - } -} - -/// Returns the ABI-normalized selector for the given Hardhat `console` function selector. -/// -/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. -pub fn hh_console_selector(input: &[u8]) -> Option<&'static Selector> { - if let Some(selector) = input.get(..4) { - let selector: &[u8; 4] = selector.try_into().unwrap(); - HARDHAT_CONSOLE_SELECTOR_PATCHES.get(selector).map(Into::into) - } else { - None - } -} - -/// Maps all the `hardhat/console.log` log selectors that use the legacy ABI (`int`, `uint`) to -/// their normalized counterparts (`int256`, `uint256`). -/// -/// `hardhat/console.log` logs its events manually, and in functions that accept integers they're -/// encoded as `abi.encodeWithSignature("log(int)", p0)`, which is not the canonical ABI encoding -/// for `int` that Solidity and [`sol!`] use. -pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: LazyLock> = - LazyLock::new(|| HashMap::from_iter(include!("./patches.rs"))); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn hardhat_console_patch() { - for (hh, generated) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { - let mut hh = *hh; - patch_hh_console_selector(&mut hh); - assert_eq!(hh, *generated); - } - } -} diff --git a/crates/evm/abi/src/console/hh.rs b/crates/evm/abi/src/console/hh.rs new file mode 100644 index 0000000000..3f4f6a240d --- /dev/null +++ b/crates/evm/abi/src/console/hh.rs @@ -0,0 +1,14 @@ +//! Hardhat `console.sol` interface. + +use alloy_sol_types::sol; +use foundry_common_fmt::*; +use foundry_macros::ConsoleFmt; + +sol!( + #[sol(abi)] + #[derive(ConsoleFmt)] + Console, + "src/Console.json" +); + +pub use Console::*; diff --git a/crates/evm/abi/src/console/mod.rs b/crates/evm/abi/src/console/mod.rs index 9ce96e4cea..1e2b23d83f 100644 --- a/crates/evm/abi/src/console/mod.rs +++ b/crates/evm/abi/src/console/mod.rs @@ -1,84 +1,7 @@ -use alloy_primitives::{hex, I256, U256}; -use alloy_sol_types::sol; -use derive_more::Display; -use itertools::Itertools; +use alloy_primitives::{I256, U256}; -mod hardhat; -pub use hardhat::*; - -// TODO: Use `UiFmt` - -sol! { -#[sol(abi)] -#[derive(Display)] -interface Console { - #[display("{val}")] - event log(string val); - - #[display("{}", hex::encode_prefixed(val))] - event logs(bytes val); - - #[display("{val}")] - event log_address(address val); - - #[display("{val}")] - event log_bytes32(bytes32 val); - - #[display("{val}")] - event log_int(int val); - - #[display("{val}")] - event log_uint(uint val); - - #[display("{}", hex::encode_prefixed(val))] - event log_bytes(bytes val); - - #[display("{val}")] - event log_string(string val); - - #[display("[{}]", val.iter().format(", "))] - event log_array(uint256[] val); - - #[display("[{}]", val.iter().format(", "))] - event log_array(int256[] val); - - #[display("[{}]", val.iter().format(", "))] - event log_array(address[] val); - - #[display("{key}: {val}")] - event log_named_address(string key, address val); - - #[display("{key}: {val}")] - event log_named_bytes32(string key, bytes32 val); - - #[display("{key}: {}", format_units_int(val, decimals))] - event log_named_decimal_int(string key, int val, uint decimals); - - #[display("{key}: {}", format_units_uint(val, decimals))] - event log_named_decimal_uint(string key, uint val, uint decimals); - - #[display("{key}: {val}")] - event log_named_int(string key, int val); - - #[display("{key}: {val}")] - event log_named_uint(string key, uint val); - - #[display("{key}: {}", hex::encode_prefixed(val))] - event log_named_bytes(string key, bytes val); - - #[display("{key}: {val}")] - event log_named_string(string key, string val); - - #[display("{key}: [{}]", val.iter().format(", "))] - event log_named_array(string key, uint256[] val); - - #[display("{key}: [{}]", val.iter().format(", "))] - event log_named_array(string key, int256[] val); - - #[display("{key}: [{}]", val.iter().format(", "))] - event log_named_array(string key, address[] val); -} -} +pub mod ds; +pub mod hh; pub fn format_units_int(x: &I256, decimals: &U256) -> String { let (sign, x) = x.into_sign_and_abs(); diff --git a/crates/evm/abi/src/console/patches.rs b/crates/evm/abi/src/console/patches.rs deleted file mode 100644 index ad63a9fe6d..0000000000 --- a/crates/evm/abi/src/console/patches.rs +++ /dev/null @@ -1,674 +0,0 @@ -[ - // `log(int)` -> `log(int256)` - // `4e0c1d1d` -> `2d5b6cb9` - ([78, 12, 29, 29], [45, 91, 108, 185]), - // `log(uint)` -> `log(uint256)` - // `f5b1bba9` -> `f82c50f1` - ([245, 177, 187, 169], [248, 44, 80, 241]), - // `log(uint)` -> `log(uint256)` - // `f5b1bba9` -> `f82c50f1` - ([245, 177, 187, 169], [248, 44, 80, 241]), - // `log(int)` -> `log(int256)` - // `4e0c1d1d` -> `2d5b6cb9` - ([78, 12, 29, 29], [45, 91, 108, 185]), - // `log(uint,uint)` -> `log(uint256,uint256)` - // `6c0f6980` -> `f666715a` - ([108, 15, 105, 128], [246, 102, 113, 90]), - // `log(uint,string)` -> `log(uint256,string)` - // `0fa3f345` -> `643fd0df` - ([15, 163, 243, 69], [100, 63, 208, 223]), - // `log(uint,bool)` -> `log(uint256,bool)` - // `1e6dd4ec` -> `1c9d7eb3` - ([30, 109, 212, 236], [28, 157, 126, 179]), - // `log(uint,address)` -> `log(uint256,address)` - // `58eb860c` -> `69276c86` - ([88, 235, 134, 12], [105, 39, 108, 134]), - // `log(string,uint)` -> `log(string,uint256)` - // `9710a9d0` -> `b60e72cc` - ([151, 16, 169, 208], [182, 14, 114, 204]), - // `log(string,int)` -> `log(string,int256)` - // `af7faa38` -> `3ca6268e` - ([175, 127, 170, 56], [60, 166, 38, 142]), - // `log(bool,uint)` -> `log(bool,uint256)` - // `364b6a92` -> `399174d3` - ([54, 75, 106, 146], [57, 145, 116, 211]), - // `log(address,uint)` -> `log(address,uint256)` - // `2243cfa3` -> `8309e8a8` - ([34, 67, 207, 163], [131, 9, 232, 168]), - // `log(uint,uint,uint)` -> `log(uint256,uint256,uint256)` - // `e7820a74` -> `d1ed7a3c` - ([231, 130, 10, 116], [209, 237, 122, 60]), - // `log(uint,uint,string)` -> `log(uint256,uint256,string)` - // `7d690ee6` -> `71d04af2` - ([125, 105, 14, 230], [113, 208, 74, 242]), - // `log(uint,uint,bool)` -> `log(uint256,uint256,bool)` - // `67570ff7` -> `4766da72` - ([103, 87, 15, 247], [71, 102, 218, 114]), - // `log(uint,uint,address)` -> `log(uint256,uint256,address)` - // `be33491b` -> `5c96b331` - ([190, 51, 73, 27], [92, 150, 179, 49]), - // `log(uint,string,uint)` -> `log(uint256,string,uint256)` - // `5b6de83f` -> `37aa7d4c` - ([91, 109, 232, 63], [55, 170, 125, 76]), - // `log(uint,string,string)` -> `log(uint256,string,string)` - // `3f57c295` -> `b115611f` - ([63, 87, 194, 149], [177, 21, 97, 31]), - // `log(uint,string,bool)` -> `log(uint256,string,bool)` - // `46a7d0ce` -> `4ceda75a` - ([70, 167, 208, 206], [76, 237, 167, 90]), - // `log(uint,string,address)` -> `log(uint256,string,address)` - // `1f90f24a` -> `7afac959` - ([31, 144, 242, 74], [122, 250, 201, 89]), - // `log(uint,bool,uint)` -> `log(uint256,bool,uint256)` - // `5a4d9922` -> `20098014` - ([90, 77, 153, 34], [32, 9, 128, 20]), - // `log(uint,bool,string)` -> `log(uint256,bool,string)` - // `8b0e14fe` -> `85775021` - ([139, 14, 20, 254], [133, 119, 80, 33]), - // `log(uint,bool,bool)` -> `log(uint256,bool,bool)` - // `d5ceace0` -> `20718650` - ([213, 206, 172, 224], [32, 113, 134, 80]), - // `log(uint,bool,address)` -> `log(uint256,bool,address)` - // `424effbf` -> `35085f7b` - ([66, 78, 255, 191], [53, 8, 95, 123]), - // `log(uint,address,uint)` -> `log(uint256,address,uint256)` - // `884343aa` -> `5a9b5ed5` - ([136, 67, 67, 170], [90, 155, 94, 213]), - // `log(uint,address,string)` -> `log(uint256,address,string)` - // `ce83047b` -> `63cb41f9` - ([206, 131, 4, 123], [99, 203, 65, 249]), - // `log(uint,address,bool)` -> `log(uint256,address,bool)` - // `7ad0128e` -> `9b6ec042` - ([122, 208, 18, 142], [155, 110, 192, 66]), - // `log(uint,address,address)` -> `log(uint256,address,address)` - // `7d77a61b` -> `bcfd9be0` - ([125, 119, 166, 27], [188, 253, 155, 224]), - // `log(string,uint,uint)` -> `log(string,uint256,uint256)` - // `969cdd03` -> `ca47c4eb` - ([150, 156, 221, 3], [202, 71, 196, 235]), - // `log(string,uint,string)` -> `log(string,uint256,string)` - // `a3f5c739` -> `5970e089` - ([163, 245, 199, 57], [89, 112, 224, 137]), - // `log(string,uint,bool)` -> `log(string,uint256,bool)` - // `f102ee05` -> `ca7733b1` - ([241, 2, 238, 5], [202, 119, 51, 177]), - // `log(string,uint,address)` -> `log(string,uint256,address)` - // `e3849f79` -> `1c7ec448` - ([227, 132, 159, 121], [28, 126, 196, 72]), - // `log(string,string,uint)` -> `log(string,string,uint256)` - // `f362ca59` -> `5821efa1` - ([243, 98, 202, 89], [88, 33, 239, 161]), - // `log(string,bool,uint)` -> `log(string,bool,uint256)` - // `291bb9d0` -> `c95958d6` - ([41, 27, 185, 208], [201, 89, 88, 214]), - // `log(string,address,uint)` -> `log(string,address,uint256)` - // `07c81217` -> `0d26b925` - ([7, 200, 18, 23], [13, 38, 185, 37]), - // `log(bool,uint,uint)` -> `log(bool,uint256,uint256)` - // `3b5c03e0` -> `37103367` - ([59, 92, 3, 224], [55, 16, 51, 103]), - // `log(bool,uint,string)` -> `log(bool,uint256,string)` - // `c8397eb0` -> `c3fc3970` - ([200, 57, 126, 176], [195, 252, 57, 112]), - // `log(bool,uint,bool)` -> `log(bool,uint256,bool)` - // `1badc9eb` -> `e8defba9` - ([27, 173, 201, 235], [232, 222, 251, 169]), - // `log(bool,uint,address)` -> `log(bool,uint256,address)` - // `c4d23507` -> `088ef9d2` - ([196, 210, 53, 7], [8, 142, 249, 210]), - // `log(bool,string,uint)` -> `log(bool,string,uint256)` - // `c0382aac` -> `1093ee11` - ([192, 56, 42, 172], [16, 147, 238, 17]), - // `log(bool,bool,uint)` -> `log(bool,bool,uint256)` - // `b01365bb` -> `12f21602` - ([176, 19, 101, 187], [18, 242, 22, 2]), - // `log(bool,address,uint)` -> `log(bool,address,uint256)` - // `eb704baf` -> `5f7b9afb` - ([235, 112, 75, 175], [95, 123, 154, 251]), - // `log(address,uint,uint)` -> `log(address,uint256,uint256)` - // `8786135e` -> `b69bcaf6` - ([135, 134, 19, 94], [182, 155, 202, 246]), - // `log(address,uint,string)` -> `log(address,uint256,string)` - // `baf96849` -> `a1f2e8aa` - ([186, 249, 104, 73], [161, 242, 232, 170]), - // `log(address,uint,bool)` -> `log(address,uint256,bool)` - // `e54ae144` -> `678209a8` - ([229, 74, 225, 68], [103, 130, 9, 168]), - // `log(address,uint,address)` -> `log(address,uint256,address)` - // `97eca394` -> `7bc0d848` - ([151, 236, 163, 148], [123, 192, 216, 72]), - // `log(address,string,uint)` -> `log(address,string,uint256)` - // `1cdaf28a` -> `67dd6ff1` - ([28, 218, 242, 138], [103, 221, 111, 241]), - // `log(address,bool,uint)` -> `log(address,bool,uint256)` - // `2c468d15` -> `9c4f99fb` - ([44, 70, 141, 21], [156, 79, 153, 251]), - // `log(address,address,uint)` -> `log(address,address,uint256)` - // `6c366d72` -> `17fe6185` - ([108, 54, 109, 114], [23, 254, 97, 133]), - // `log(uint,uint,uint,uint)` -> `log(uint256,uint256,uint256,uint256)` - // `5ca0ad3e` -> `193fb800` - ([92, 160, 173, 62], [25, 63, 184, 0]), - // `log(uint,uint,uint,string)` -> `log(uint256,uint256,uint256,string)` - // `78ad7a0c` -> `59cfcbe3` - ([120, 173, 122, 12], [89, 207, 203, 227]), - // `log(uint,uint,uint,bool)` -> `log(uint256,uint256,uint256,bool)` - // `6452b9cb` -> `c598d185` - ([100, 82, 185, 203], [197, 152, 209, 133]), - // `log(uint,uint,uint,address)` -> `log(uint256,uint256,uint256,address)` - // `e0853f69` -> `fa8185af` - ([224, 133, 63, 105], [250, 129, 133, 175]), - // `log(uint,uint,string,uint)` -> `log(uint256,uint256,string,uint256)` - // `3894163d` -> `5da297eb` - ([56, 148, 22, 61], [93, 162, 151, 235]), - // `log(uint,uint,string,string)` -> `log(uint256,uint256,string,string)` - // `7c032a32` -> `27d8afd2` - ([124, 3, 42, 50], [39, 216, 175, 210]), - // `log(uint,uint,string,bool)` -> `log(uint256,uint256,string,bool)` - // `b22eaf06` -> `7af6ab25` - ([178, 46, 175, 6], [122, 246, 171, 37]), - // `log(uint,uint,string,address)` -> `log(uint256,uint256,string,address)` - // `433285a2` -> `42d21db7` - ([67, 50, 133, 162], [66, 210, 29, 183]), - // `log(uint,uint,bool,uint)` -> `log(uint256,uint256,bool,uint256)` - // `6c647c8c` -> `eb7f6fd2` - ([108, 100, 124, 140], [235, 127, 111, 210]), - // `log(uint,uint,bool,string)` -> `log(uint256,uint256,bool,string)` - // `efd9cbee` -> `a5b4fc99` - ([239, 217, 203, 238], [165, 180, 252, 153]), - // `log(uint,uint,bool,bool)` -> `log(uint256,uint256,bool,bool)` - // `94be3bb1` -> `ab085ae6` - ([148, 190, 59, 177], [171, 8, 90, 230]), - // `log(uint,uint,bool,address)` -> `log(uint256,uint256,bool,address)` - // `e117744f` -> `9a816a83` - ([225, 23, 116, 79], [154, 129, 106, 131]), - // `log(uint,uint,address,uint)` -> `log(uint256,uint256,address,uint256)` - // `610ba8c0` -> `88f6e4b2` - ([97, 11, 168, 192], [136, 246, 228, 178]), - // `log(uint,uint,address,string)` -> `log(uint256,uint256,address,string)` - // `d6a2d1de` -> `6cde40b8` - ([214, 162, 209, 222], [108, 222, 64, 184]), - // `log(uint,uint,address,bool)` -> `log(uint256,uint256,address,bool)` - // `a8e820ae` -> `15cac476` - ([168, 232, 32, 174], [21, 202, 196, 118]), - // `log(uint,uint,address,address)` -> `log(uint256,uint256,address,address)` - // `ca939b20` -> `56a5d1b1` - ([202, 147, 155, 32], [86, 165, 209, 177]), - // `log(uint,string,uint,uint)` -> `log(uint256,string,uint256,uint256)` - // `c0043807` -> `82c25b74` - ([192, 4, 56, 7], [130, 194, 91, 116]), - // `log(uint,string,uint,string)` -> `log(uint256,string,uint256,string)` - // `a2bc0c99` -> `b7b914ca` - ([162, 188, 12, 153], [183, 185, 20, 202]), - // `log(uint,string,uint,bool)` -> `log(uint256,string,uint256,bool)` - // `875a6e2e` -> `691a8f74` - ([135, 90, 110, 46], [105, 26, 143, 116]), - // `log(uint,string,uint,address)` -> `log(uint256,string,uint256,address)` - // `ab7bd9fd` -> `3b2279b4` - ([171, 123, 217, 253], [59, 34, 121, 180]), - // `log(uint,string,string,uint)` -> `log(uint256,string,string,uint256)` - // `76ec635e` -> `b028c9bd` - ([118, 236, 99, 94], [176, 40, 201, 189]), - // `log(uint,string,string,string)` -> `log(uint256,string,string,string)` - // `57dd0a11` -> `21ad0683` - ([87, 221, 10, 17], [33, 173, 6, 131]), - // `log(uint,string,string,bool)` -> `log(uint256,string,string,bool)` - // `12862b98` -> `b3a6b6bd` - ([18, 134, 43, 152], [179, 166, 182, 189]), - // `log(uint,string,string,address)` -> `log(uint256,string,string,address)` - // `cc988aa0` -> `d583c602` - ([204, 152, 138, 160], [213, 131, 198, 2]), - // `log(uint,string,bool,uint)` -> `log(uint256,string,bool,uint256)` - // `a4b48a7f` -> `cf009880` - ([164, 180, 138, 127], [207, 0, 152, 128]), - // `log(uint,string,bool,string)` -> `log(uint256,string,bool,string)` - // `8d489ca0` -> `d2d423cd` - ([141, 72, 156, 160], [210, 212, 35, 205]), - // `log(uint,string,bool,bool)` -> `log(uint256,string,bool,bool)` - // `51bc2bc1` -> `ba535d9c` - ([81, 188, 43, 193], [186, 83, 93, 156]), - // `log(uint,string,bool,address)` -> `log(uint256,string,bool,address)` - // `796f28a0` -> `ae2ec581` - ([121, 111, 40, 160], [174, 46, 197, 129]), - // `log(uint,string,address,uint)` -> `log(uint256,string,address,uint256)` - // `98e7f3f3` -> `e8d3018d` - ([152, 231, 243, 243], [232, 211, 1, 141]), - // `log(uint,string,address,string)` -> `log(uint256,string,address,string)` - // `f898577f` -> `9c3adfa1` - ([248, 152, 87, 127], [156, 58, 223, 161]), - // `log(uint,string,address,bool)` -> `log(uint256,string,address,bool)` - // `f93fff37` -> `90c30a56` - ([249, 63, 255, 55], [144, 195, 10, 86]), - // `log(uint,string,address,address)` -> `log(uint256,string,address,address)` - // `7fa5458b` -> `6168ed61` - ([127, 165, 69, 139], [97, 104, 237, 97]), - // `log(uint,bool,uint,uint)` -> `log(uint256,bool,uint256,uint256)` - // `56828da4` -> `c6acc7a8` - ([86, 130, 141, 164], [198, 172, 199, 168]), - // `log(uint,bool,uint,string)` -> `log(uint256,bool,uint256,string)` - // `e8ddbc56` -> `de03e774` - ([232, 221, 188, 86], [222, 3, 231, 116]), - // `log(uint,bool,uint,bool)` -> `log(uint256,bool,uint256,bool)` - // `d2abc4fd` -> `91a02e2a` - ([210, 171, 196, 253], [145, 160, 46, 42]), - // `log(uint,bool,uint,address)` -> `log(uint256,bool,uint256,address)` - // `4f40058e` -> `88cb6041` - ([79, 64, 5, 142], [136, 203, 96, 65]), - // `log(uint,bool,string,uint)` -> `log(uint256,bool,string,uint256)` - // `915fdb28` -> `2c1d0746` - ([145, 95, 219, 40], [44, 29, 7, 70]), - // `log(uint,bool,string,string)` -> `log(uint256,bool,string,string)` - // `a433fcfd` -> `68c8b8bd` - ([164, 51, 252, 253], [104, 200, 184, 189]), - // `log(uint,bool,string,bool)` -> `log(uint256,bool,string,bool)` - // `346eb8c7` -> `eb928d7f` - ([52, 110, 184, 199], [235, 146, 141, 127]), - // `log(uint,bool,string,address)` -> `log(uint256,bool,string,address)` - // `496e2bb4` -> `ef529018` - ([73, 110, 43, 180], [239, 82, 144, 24]), - // `log(uint,bool,bool,uint)` -> `log(uint256,bool,bool,uint256)` - // `bd25ad59` -> `7464ce23` - ([189, 37, 173, 89], [116, 100, 206, 35]), - // `log(uint,bool,bool,string)` -> `log(uint256,bool,bool,string)` - // `318ae59b` -> `dddb9561` - ([49, 138, 229, 155], [221, 219, 149, 97]), - // `log(uint,bool,bool,bool)` -> `log(uint256,bool,bool,bool)` - // `4e6c5315` -> `b6f577a1` - ([78, 108, 83, 21], [182, 245, 119, 161]), - // `log(uint,bool,bool,address)` -> `log(uint256,bool,bool,address)` - // `5306225d` -> `69640b59` - ([83, 6, 34, 93], [105, 100, 11, 89]), - // `log(uint,bool,address,uint)` -> `log(uint256,bool,address,uint256)` - // `41b5ef3b` -> `078287f5` - ([65, 181, 239, 59], [7, 130, 135, 245]), - // `log(uint,bool,address,string)` -> `log(uint256,bool,address,string)` - // `a230761e` -> `ade052c7` - ([162, 48, 118, 30], [173, 224, 82, 199]), - // `log(uint,bool,address,bool)` -> `log(uint256,bool,address,bool)` - // `91fb1242` -> `454d54a5` - ([145, 251, 18, 66], [69, 77, 84, 165]), - // `log(uint,bool,address,address)` -> `log(uint256,bool,address,address)` - // `86edc10c` -> `a1ef4cbb` - ([134, 237, 193, 12], [161, 239, 76, 187]), - // `log(uint,address,uint,uint)` -> `log(uint256,address,uint256,uint256)` - // `ca9a3eb4` -> `0c9cd9c1` - ([202, 154, 62, 180], [12, 156, 217, 193]), - // `log(uint,address,uint,string)` -> `log(uint256,address,uint256,string)` - // `3ed3bd28` -> `ddb06521` - ([62, 211, 189, 40], [221, 176, 101, 33]), - // `log(uint,address,uint,bool)` -> `log(uint256,address,uint256,bool)` - // `19f67369` -> `5f743a7c` - ([25, 246, 115, 105], [95, 116, 58, 124]), - // `log(uint,address,uint,address)` -> `log(uint256,address,uint256,address)` - // `fdb2ecd4` -> `15c127b5` - ([253, 178, 236, 212], [21, 193, 39, 181]), - // `log(uint,address,string,uint)` -> `log(uint256,address,string,uint256)` - // `a0c414e8` -> `46826b5d` - ([160, 196, 20, 232], [70, 130, 107, 93]), - // `log(uint,address,string,string)` -> `log(uint256,address,string,string)` - // `8d778624` -> `3e128ca3` - ([141, 119, 134, 36], [62, 18, 140, 163]), - // `log(uint,address,string,bool)` -> `log(uint256,address,string,bool)` - // `22a479a6` -> `cc32ab07` - ([34, 164, 121, 166], [204, 50, 171, 7]), - // `log(uint,address,string,address)` -> `log(uint256,address,string,address)` - // `cbe58efd` -> `9cba8fff` - ([203, 229, 142, 253], [156, 186, 143, 255]), - // `log(uint,address,bool,uint)` -> `log(uint256,address,bool,uint256)` - // `7b08e8eb` -> `5abd992a` - ([123, 8, 232, 235], [90, 189, 153, 42]), - // `log(uint,address,bool,string)` -> `log(uint256,address,bool,string)` - // `63f0e242` -> `90fb06aa` - ([99, 240, 226, 66], [144, 251, 6, 170]), - // `log(uint,address,bool,bool)` -> `log(uint256,address,bool,bool)` - // `7e27410d` -> `e351140f` - ([126, 39, 65, 13], [227, 81, 20, 15]), - // `log(uint,address,bool,address)` -> `log(uint256,address,bool,address)` - // `b6313094` -> `ef72c513` - ([182, 49, 48, 148], [239, 114, 197, 19]), - // `log(uint,address,address,uint)` -> `log(uint256,address,address,uint256)` - // `9a3cbf96` -> `736efbb6` - ([154, 60, 191, 150], [115, 110, 251, 182]), - // `log(uint,address,address,string)` -> `log(uint256,address,address,string)` - // `7943dc66` -> `031c6f73` - ([121, 67, 220, 102], [3, 28, 111, 115]), - // `log(uint,address,address,bool)` -> `log(uint256,address,address,bool)` - // `01550b04` -> `091ffaf5` - ([1, 85, 11, 4], [9, 31, 250, 245]), - // `log(uint,address,address,address)` -> `log(uint256,address,address,address)` - // `554745f9` -> `2488b414` - ([85, 71, 69, 249], [36, 136, 180, 20]), - // `log(string,uint,uint,uint)` -> `log(string,uint256,uint256,uint256)` - // `08ee5666` -> `a7a87853` - ([8, 238, 86, 102], [167, 168, 120, 83]), - // `log(string,uint,uint,string)` -> `log(string,uint256,uint256,string)` - // `a54ed4bd` -> `854b3496` - ([165, 78, 212, 189], [133, 75, 52, 150]), - // `log(string,uint,uint,bool)` -> `log(string,uint256,uint256,bool)` - // `f73c7e3d` -> `7626db92` - ([247, 60, 126, 61], [118, 38, 219, 146]), - // `log(string,uint,uint,address)` -> `log(string,uint256,uint256,address)` - // `bed728bf` -> `e21de278` - ([190, 215, 40, 191], [226, 29, 226, 120]), - // `log(string,uint,string,uint)` -> `log(string,uint256,string,uint256)` - // `a0c4b225` -> `c67ea9d1` - ([160, 196, 178, 37], [198, 126, 169, 209]), - // `log(string,uint,string,string)` -> `log(string,uint256,string,string)` - // `6c98dae2` -> `5ab84e1f` - ([108, 152, 218, 226], [90, 184, 78, 31]), - // `log(string,uint,string,bool)` -> `log(string,uint256,string,bool)` - // `e99f82cf` -> `7d24491d` - ([233, 159, 130, 207], [125, 36, 73, 29]), - // `log(string,uint,string,address)` -> `log(string,uint256,string,address)` - // `bb7235e9` -> `7c4632a4` - ([187, 114, 53, 233], [124, 70, 50, 164]), - // `log(string,uint,bool,uint)` -> `log(string,uint256,bool,uint256)` - // `550e6ef5` -> `e41b6f6f` - ([85, 14, 110, 245], [228, 27, 111, 111]), - // `log(string,uint,bool,string)` -> `log(string,uint256,bool,string)` - // `76cc6064` -> `abf73a98` - ([118, 204, 96, 100], [171, 247, 58, 152]), - // `log(string,uint,bool,bool)` -> `log(string,uint256,bool,bool)` - // `e37ff3d0` -> `354c36d6` - ([227, 127, 243, 208], [53, 76, 54, 214]), - // `log(string,uint,bool,address)` -> `log(string,uint256,bool,address)` - // `e5549d91` -> `e0e95b98` - ([229, 84, 157, 145], [224, 233, 91, 152]), - // `log(string,uint,address,uint)` -> `log(string,uint256,address,uint256)` - // `58497afe` -> `4f04fdc6` - ([88, 73, 122, 254], [79, 4, 253, 198]), - // `log(string,uint,address,string)` -> `log(string,uint256,address,string)` - // `3254c2e8` -> `9ffb2f93` - ([50, 84, 194, 232], [159, 251, 47, 147]), - // `log(string,uint,address,bool)` -> `log(string,uint256,address,bool)` - // `1106a8f7` -> `82112a42` - ([17, 6, 168, 247], [130, 17, 42, 66]), - // `log(string,uint,address,address)` -> `log(string,uint256,address,address)` - // `eac89281` -> `5ea2b7ae` - ([234, 200, 146, 129], [94, 162, 183, 174]), - // `log(string,string,uint,uint)` -> `log(string,string,uint256,uint256)` - // `d5cf17d0` -> `f45d7d2c` - ([213, 207, 23, 208], [244, 93, 125, 44]), - // `log(string,string,uint,string)` -> `log(string,string,uint256,string)` - // `8d142cdd` -> `5d1a971a` - ([141, 20, 44, 221], [93, 26, 151, 26]), - // `log(string,string,uint,bool)` -> `log(string,string,uint256,bool)` - // `e65658ca` -> `c3a8a654` - ([230, 86, 88, 202], [195, 168, 166, 84]), - // `log(string,string,uint,address)` -> `log(string,string,uint256,address)` - // `5d4f4680` -> `1023f7b2` - ([93, 79, 70, 128], [16, 35, 247, 178]), - // `log(string,string,string,uint)` -> `log(string,string,string,uint256)` - // `9fd009f5` -> `8eafb02b` - ([159, 208, 9, 245], [142, 175, 176, 43]), - // `log(string,string,bool,uint)` -> `log(string,string,bool,uint256)` - // `86818a7a` -> `d6aefad2` - ([134, 129, 138, 122], [214, 174, 250, 210]), - // `log(string,string,address,uint)` -> `log(string,string,address,uint256)` - // `4a81a56a` -> `7cc3c607` - ([74, 129, 165, 106], [124, 195, 198, 7]), - // `log(string,bool,uint,uint)` -> `log(string,bool,uint256,uint256)` - // `5dbff038` -> `64b5bb67` - ([93, 191, 240, 56], [100, 181, 187, 103]), - // `log(string,bool,uint,string)` -> `log(string,bool,uint256,string)` - // `42b9a227` -> `742d6ee7` - ([66, 185, 162, 39], [116, 45, 110, 231]), - // `log(string,bool,uint,bool)` -> `log(string,bool,uint256,bool)` - // `3cc5b5d3` -> `8af7cf8a` - ([60, 197, 181, 211], [138, 247, 207, 138]), - // `log(string,bool,uint,address)` -> `log(string,bool,uint256,address)` - // `71d3850d` -> `935e09bf` - ([113, 211, 133, 13], [147, 94, 9, 191]), - // `log(string,bool,string,uint)` -> `log(string,bool,string,uint256)` - // `34cb308d` -> `24f91465` - ([52, 203, 48, 141], [36, 249, 20, 101]), - // `log(string,bool,bool,uint)` -> `log(string,bool,bool,uint256)` - // `807531e8` -> `8e3f78a9` - ([128, 117, 49, 232], [142, 63, 120, 169]), - // `log(string,bool,address,uint)` -> `log(string,bool,address,uint256)` - // `28df4e96` -> `5d08bb05` - ([40, 223, 78, 150], [93, 8, 187, 5]), - // `log(string,address,uint,uint)` -> `log(string,address,uint256,uint256)` - // `daa394bd` -> `f8f51b1e` - ([218, 163, 148, 189], [248, 245, 27, 30]), - // `log(string,address,uint,string)` -> `log(string,address,uint256,string)` - // `4c55f234` -> `5a477632` - ([76, 85, 242, 52], [90, 71, 118, 50]), - // `log(string,address,uint,bool)` -> `log(string,address,uint256,bool)` - // `5ac1c13c` -> `fc4845f0` - ([90, 193, 193, 60], [252, 72, 69, 240]), - // `log(string,address,uint,address)` -> `log(string,address,uint256,address)` - // `a366ec80` -> `63fb8bc5` - ([163, 102, 236, 128], [99, 251, 139, 197]), - // `log(string,address,string,uint)` -> `log(string,address,string,uint256)` - // `8f624be9` -> `91d1112e` - ([143, 98, 75, 233], [145, 209, 17, 46]), - // `log(string,address,bool,uint)` -> `log(string,address,bool,uint256)` - // `c5d1bb8b` -> `3e9f866a` - ([197, 209, 187, 139], [62, 159, 134, 106]), - // `log(string,address,address,uint)` -> `log(string,address,address,uint256)` - // `6eb7943d` -> `8ef3f399` - ([110, 183, 148, 61], [142, 243, 243, 153]), - // `log(bool,uint,uint,uint)` -> `log(bool,uint256,uint256,uint256)` - // `32dfa524` -> `374bb4b2` - ([50, 223, 165, 36], [55, 75, 180, 178]), - // `log(bool,uint,uint,string)` -> `log(bool,uint256,uint256,string)` - // `da0666c8` -> `8e69fb5d` - ([218, 6, 102, 200], [142, 105, 251, 93]), - // `log(bool,uint,uint,bool)` -> `log(bool,uint256,uint256,bool)` - // `a41d81de` -> `be984353` - ([164, 29, 129, 222], [190, 152, 67, 83]), - // `log(bool,uint,uint,address)` -> `log(bool,uint256,uint256,address)` - // `f161b221` -> `00dd87b9` - ([241, 97, 178, 33], [0, 221, 135, 185]), - // `log(bool,uint,string,uint)` -> `log(bool,uint256,string,uint256)` - // `4180011b` -> `6a1199e2` - ([65, 128, 1, 27], [106, 17, 153, 226]), - // `log(bool,uint,string,string)` -> `log(bool,uint256,string,string)` - // `d32a6548` -> `f5bc2249` - ([211, 42, 101, 72], [245, 188, 34, 73]), - // `log(bool,uint,string,bool)` -> `log(bool,uint256,string,bool)` - // `91d2f813` -> `e5e70b2b` - ([145, 210, 248, 19], [229, 231, 11, 43]), - // `log(bool,uint,string,address)` -> `log(bool,uint256,string,address)` - // `a5c70d29` -> `fedd1fff` - ([165, 199, 13, 41], [254, 221, 31, 255]), - // `log(bool,uint,bool,uint)` -> `log(bool,uint256,bool,uint256)` - // `d3de5593` -> `7f9bbca2` - ([211, 222, 85, 147], [127, 155, 188, 162]), - // `log(bool,uint,bool,string)` -> `log(bool,uint256,bool,string)` - // `b6d569d4` -> `9143dbb1` - ([182, 213, 105, 212], [145, 67, 219, 177]), - // `log(bool,uint,bool,bool)` -> `log(bool,uint256,bool,bool)` - // `9e01f741` -> `ceb5f4d7` - ([158, 1, 247, 65], [206, 181, 244, 215]), - // `log(bool,uint,bool,address)` -> `log(bool,uint256,bool,address)` - // `4267c7f8` -> `9acd3616` - ([66, 103, 199, 248], [154, 205, 54, 22]), - // `log(bool,uint,address,uint)` -> `log(bool,uint256,address,uint256)` - // `caa5236a` -> `1537dc87` - ([202, 165, 35, 106], [21, 55, 220, 135]), - // `log(bool,uint,address,string)` -> `log(bool,uint256,address,string)` - // `18091341` -> `1bb3b09a` - ([24, 9, 19, 65], [27, 179, 176, 154]), - // `log(bool,uint,address,bool)` -> `log(bool,uint256,address,bool)` - // `65adf408` -> `b4c314ff` - ([101, 173, 244, 8], [180, 195, 20, 255]), - // `log(bool,uint,address,address)` -> `log(bool,uint256,address,address)` - // `8a2f90aa` -> `26f560a8` - ([138, 47, 144, 170], [38, 245, 96, 168]), - // `log(bool,string,uint,uint)` -> `log(bool,string,uint256,uint256)` - // `8e4ae86e` -> `28863fcb` - ([142, 74, 232, 110], [40, 134, 63, 203]), - // `log(bool,string,uint,string)` -> `log(bool,string,uint256,string)` - // `77a1abed` -> `1ad96de6` - ([119, 161, 171, 237], [26, 217, 109, 230]), - // `log(bool,string,uint,bool)` -> `log(bool,string,uint256,bool)` - // `20bbc9af` -> `6b0e5d53` - ([32, 187, 201, 175], [107, 14, 93, 83]), - // `log(bool,string,uint,address)` -> `log(bool,string,uint256,address)` - // `5b22b938` -> `1596a1ce` - ([91, 34, 185, 56], [21, 150, 161, 206]), - // `log(bool,string,string,uint)` -> `log(bool,string,string,uint256)` - // `5ddb2592` -> `7be0c3eb` - ([93, 219, 37, 146], [123, 224, 195, 235]), - // `log(bool,string,bool,uint)` -> `log(bool,string,bool,uint256)` - // `8d6f9ca5` -> `1606a393` - ([141, 111, 156, 165], [22, 6, 163, 147]), - // `log(bool,string,address,uint)` -> `log(bool,string,address,uint256)` - // `1b0b955b` -> `a5cada94` - ([27, 11, 149, 91], [165, 202, 218, 148]), - // `log(bool,bool,uint,uint)` -> `log(bool,bool,uint256,uint256)` - // `4667de8e` -> `0bb00eab` - ([70, 103, 222, 142], [11, 176, 14, 171]), - // `log(bool,bool,uint,string)` -> `log(bool,bool,uint256,string)` - // `50618937` -> `7dd4d0e0` - ([80, 97, 137, 55], [125, 212, 208, 224]), - // `log(bool,bool,uint,bool)` -> `log(bool,bool,uint256,bool)` - // `ab5cc1c4` -> `619e4d0e` - ([171, 92, 193, 196], [97, 158, 77, 14]), - // `log(bool,bool,uint,address)` -> `log(bool,bool,uint256,address)` - // `0bff950d` -> `54a7a9a0` - ([11, 255, 149, 13], [84, 167, 169, 160]), - // `log(bool,bool,string,uint)` -> `log(bool,bool,string,uint256)` - // `178b4685` -> `e3a9ca2f` - ([23, 139, 70, 133], [227, 169, 202, 47]), - // `log(bool,bool,bool,uint)` -> `log(bool,bool,bool,uint256)` - // `c248834d` -> `6d7045c1` - ([194, 72, 131, 77], [109, 112, 69, 193]), - // `log(bool,bool,address,uint)` -> `log(bool,bool,address,uint256)` - // `609386e7` -> `4c123d57` - ([96, 147, 134, 231], [76, 18, 61, 87]), - // `log(bool,address,uint,uint)` -> `log(bool,address,uint256,uint256)` - // `9bfe72bc` -> `7bf181a1` - ([155, 254, 114, 188], [123, 241, 129, 161]), - // `log(bool,address,uint,string)` -> `log(bool,address,uint256,string)` - // `a0685833` -> `51f09ff8` - ([160, 104, 88, 51], [81, 240, 159, 248]), - // `log(bool,address,uint,bool)` -> `log(bool,address,uint256,bool)` - // `ee8d8672` -> `d6019f1c` - ([238, 141, 134, 114], [214, 1, 159, 28]), - // `log(bool,address,uint,address)` -> `log(bool,address,uint256,address)` - // `68f158b5` -> `136b05dd` - ([104, 241, 88, 181], [19, 107, 5, 221]), - // `log(bool,address,string,uint)` -> `log(bool,address,string,uint256)` - // `0b99fc22` -> `c21f64c7` - ([11, 153, 252, 34], [194, 31, 100, 199]), - // `log(bool,address,bool,uint)` -> `log(bool,address,bool,uint256)` - // `4cb60fd1` -> `07831502` - ([76, 182, 15, 209], [7, 131, 21, 2]), - // `log(bool,address,address,uint)` -> `log(bool,address,address,uint256)` - // `5284bd6c` -> `0c66d1be` - ([82, 132, 189, 108], [12, 102, 209, 190]), - // `log(address,uint,uint,uint)` -> `log(address,uint256,uint256,uint256)` - // `3d0e9de4` -> `34f0e636` - ([61, 14, 157, 228], [52, 240, 230, 54]), - // `log(address,uint,uint,string)` -> `log(address,uint256,uint256,string)` - // `89340dab` -> `4a28c017` - ([137, 52, 13, 171], [74, 40, 192, 23]), - // `log(address,uint,uint,bool)` -> `log(address,uint256,uint256,bool)` - // `ec4ba8a2` -> `66f1bc67` - ([236, 75, 168, 162], [102, 241, 188, 103]), - // `log(address,uint,uint,address)` -> `log(address,uint256,uint256,address)` - // `1ef63434` -> `20e3984d` - ([30, 246, 52, 52], [32, 227, 152, 77]), - // `log(address,uint,string,uint)` -> `log(address,uint256,string,uint256)` - // `f512cf9b` -> `bf01f891` - ([245, 18, 207, 155], [191, 1, 248, 145]), - // `log(address,uint,string,string)` -> `log(address,uint256,string,string)` - // `7e56c693` -> `88a8c406` - ([126, 86, 198, 147], [136, 168, 196, 6]), - // `log(address,uint,string,bool)` -> `log(address,uint256,string,bool)` - // `a4024f11` -> `cf18105c` - ([164, 2, 79, 17], [207, 24, 16, 92]), - // `log(address,uint,string,address)` -> `log(address,uint256,string,address)` - // `dc792604` -> `5c430d47` - ([220, 121, 38, 4], [92, 67, 13, 71]), - // `log(address,uint,bool,uint)` -> `log(address,uint256,bool,uint256)` - // `698f4392` -> `22f6b999` - ([105, 143, 67, 146], [34, 246, 185, 153]), - // `log(address,uint,bool,string)` -> `log(address,uint256,bool,string)` - // `8e8e4e75` -> `c5ad85f9` - ([142, 142, 78, 117], [197, 173, 133, 249]), - // `log(address,uint,bool,bool)` -> `log(address,uint256,bool,bool)` - // `fea1d55a` -> `3bf5e537` - ([254, 161, 213, 90], [59, 245, 229, 55]), - // `log(address,uint,bool,address)` -> `log(address,uint256,bool,address)` - // `23e54972` -> `a31bfdcc` - ([35, 229, 73, 114], [163, 27, 253, 204]), - // `log(address,uint,address,uint)` -> `log(address,uint256,address,uint256)` - // `a5d98768` -> `100f650e` - ([165, 217, 135, 104], [16, 15, 101, 14]), - // `log(address,uint,address,string)` -> `log(address,uint256,address,string)` - // `5d71f39e` -> `1da986ea` - ([93, 113, 243, 158], [29, 169, 134, 234]), - // `log(address,uint,address,bool)` -> `log(address,uint256,address,bool)` - // `f181a1e9` -> `a1bcc9b3` - ([241, 129, 161, 233], [161, 188, 201, 179]), - // `log(address,uint,address,address)` -> `log(address,uint256,address,address)` - // `ec24846f` -> `478d1c62` - ([236, 36, 132, 111], [71, 141, 28, 98]), - // `log(address,string,uint,uint)` -> `log(address,string,uint256,uint256)` - // `a4c92a60` -> `1dc8e1b8` - ([164, 201, 42, 96], [29, 200, 225, 184]), - // `log(address,string,uint,string)` -> `log(address,string,uint256,string)` - // `5d1365c9` -> `448830a8` - ([93, 19, 101, 201], [68, 136, 48, 168]), - // `log(address,string,uint,bool)` -> `log(address,string,uint256,bool)` - // `7e250d5b` -> `0ef7e050` - ([126, 37, 13, 91], [14, 247, 224, 80]), - // `log(address,string,uint,address)` -> `log(address,string,uint256,address)` - // `dfd7d80b` -> `63183678` - ([223, 215, 216, 11], [99, 24, 54, 120]), - // `log(address,string,string,uint)` -> `log(address,string,string,uint256)` - // `a14fd039` -> `159f8927` - ([161, 79, 208, 57], [21, 159, 137, 39]), - // `log(address,string,bool,uint)` -> `log(address,string,bool,uint256)` - // `e720521c` -> `515e38b6` - ([231, 32, 82, 28], [81, 94, 56, 182]), - // `log(address,string,address,uint)` -> `log(address,string,address,uint256)` - // `8c1933a9` -> `457fe3cf` - ([140, 25, 51, 169], [69, 127, 227, 207]), - // `log(address,bool,uint,uint)` -> `log(address,bool,uint256,uint256)` - // `c210a01e` -> `386ff5f4` - ([194, 16, 160, 30], [56, 111, 245, 244]), - // `log(address,bool,uint,string)` -> `log(address,bool,uint256,string)` - // `9b588ecc` -> `0aa6cfad` - ([155, 88, 142, 204], [10, 166, 207, 173]), - // `log(address,bool,uint,bool)` -> `log(address,bool,uint256,bool)` - // `85cdc5af` -> `c4643e20` - ([133, 205, 197, 175], [196, 100, 62, 32]), - // `log(address,bool,uint,address)` -> `log(address,bool,uint256,address)` - // `0d8ce61e` -> `ccf790a1` - ([13, 140, 230, 30], [204, 247, 144, 161]), - // `log(address,bool,string,uint)` -> `log(address,bool,string,uint256)` - // `9e127b6e` -> `80e6a20b` - ([158, 18, 123, 110], [128, 230, 162, 11]), - // `log(address,bool,bool,uint)` -> `log(address,bool,bool,uint256)` - // `cfb58756` -> `8c4e5de6` - ([207, 181, 135, 86], [140, 78, 93, 230]), - // `log(address,bool,address,uint)` -> `log(address,bool,address,uint256)` - // `dc7116d2` -> `a75c59de` - ([220, 113, 22, 210], [167, 92, 89, 222]), - // `log(address,address,uint,uint)` -> `log(address,address,uint256,uint256)` - // `54fdf3e4` -> `be553481` - ([84, 253, 243, 228], [190, 85, 52, 129]), - // `log(address,address,uint,string)` -> `log(address,address,uint256,string)` - // `9dd12ead` -> `fdb4f990` - ([157, 209, 46, 173], [253, 180, 249, 144]), - // `log(address,address,uint,bool)` -> `log(address,address,uint256,bool)` - // `c2f688ec` -> `9b4254e2` - ([194, 246, 136, 236], [155, 66, 84, 226]), - // `log(address,address,uint,address)` -> `log(address,address,uint256,address)` - // `d6c65276` -> `8da6def5` - ([214, 198, 82, 118], [141, 166, 222, 245]), - // `log(address,address,string,uint)` -> `log(address,address,string,uint256)` - // `04289300` -> `ef1cefe7` - ([4, 40, 147, 0], [239, 28, 239, 231]), - // `log(address,address,bool,uint)` -> `log(address,address,bool,uint256)` - // `95d65f11` -> `3971e78c` - ([149, 214, 95, 17], [57, 113, 231, 140]), - // `log(address,address,address,uint)` -> `log(address,address,address,uint256)` - // `ed5eac87` -> `94250d77` - ([237, 94, 172, 135], [148, 37, 13, 119]), -] diff --git a/crates/evm/abi/src/lib.rs b/crates/evm/abi/src/lib.rs index 6a31fe5509..8e9313c2f5 100644 --- a/crates/evm/abi/src/lib.rs +++ b/crates/evm/abi/src/lib.rs @@ -3,5 +3,4 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -mod console; -pub use console::*; +pub mod console; diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index c4555ae866..88e5eaa88b 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -15,6 +15,11 @@ pub const CHEATCODE_ADDRESS: Address = address!("7109709ECfa91a80626fF3989D68f67 pub const CHEATCODE_CONTRACT_HASH: B256 = b256!("b0450508e5a2349057c3b4c9c84524d62be4bb17e565dbe2df34725a26872291"); +/// The Hardhat console address. +/// +/// See: +pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); + /// Stores the caller address to be used as *sender* account for: /// - deploying Test contracts /// - deploying Script contracts diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 37ff286d06..ef1e54daba 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -1,12 +1,11 @@ //! Various utilities to decode test results. +use crate::abi::{console, Vm}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Error, JsonAbi}; use alloy_primitives::{hex, map::HashMap, Log, Selector}; use alloy_sol_types::{SolEventInterface, SolInterface, SolValue}; -use foundry_cheatcodes_spec::Vm; use foundry_common::SELECTOR_LEN; -use foundry_evm_abi::Console; use itertools::Itertools; use revm::interpreter::InstructionResult; use std::{fmt, sync::OnceLock}; @@ -53,7 +52,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`. pub fn decode_console_log(log: &Log) -> Option { - Console::ConsoleEvents::decode_log(log, false).ok().map(|decoded| decoded.to_string()) + console::ds::ConsoleEvents::decode_log(log, false).ok().map(|decoded| decoded.to_string()) } /// Decodes revert data. diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index ae3f9b7a04..7730eb6afb 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -23,7 +23,7 @@ pub async fn environment>( 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")? + 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(), @@ -44,12 +44,11 @@ pub async fn environment>( error!("{NON_ARCHIVE_NODE_WARNING}"); } eyre::bail!( - "Failed to get block for block number: {}\nlatest block number: {}", - block_number, - latest_block + "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) + eyre::bail!("failed to get block for block number: {block_number}") }; let mut cfg = CfgEnv::default(); diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index fcabf2a18b..80fef528c9 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,17 +1,19 @@ -use alloy_primitives::map::HashMap; +use alloy_primitives::map::rustc_hash::FxHashMap; use eyre::Result; use revm::interpreter::{ opcode::{PUSH0, PUSH1, PUSH32}, OpCode, }; use revm_inspectors::opcode::immediate_size; +use serde::Serialize; /// Maps from program counter to instruction counter. /// /// Inverse of [`IcPcMap`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] pub struct PcIcMap { - pub inner: HashMap, + pub inner: FxHashMap, } impl PcIcMap { @@ -31,7 +33,7 @@ impl PcIcMap { } /// Returns the instruction counter for the given program counter. - pub fn get(&self, pc: usize) -> Option { + pub fn get(&self, pc: u32) -> Option { self.inner.get(&pc).copied() } } @@ -40,7 +42,7 @@ impl PcIcMap { /// /// Inverse of [`PcIcMap`]. pub struct IcPcMap { - pub inner: HashMap, + pub inner: FxHashMap, } impl IcPcMap { @@ -60,22 +62,24 @@ impl IcPcMap { } /// Returns the program counter for the given instruction counter. - pub fn get(&self, ic: usize) -> Option { + pub fn get(&self, ic: u32) -> Option { self.inner.get(&ic).copied() } } -fn make_map(code: &[u8]) -> HashMap { - let mut map = HashMap::default(); +fn make_map(code: &[u8]) -> FxHashMap { + assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); - let mut pc = 0; - let mut cumulative_push_size = 0; + 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; if PC_FIRST { - map.insert(pc, ic); + map.insert(pc as u32, ic as u32); } else { - map.insert(ic, pc); + map.insert(ic as u32, pc as u32); } if (PUSH1..=PUSH32).contains(&code[pc]) { @@ -87,6 +91,9 @@ fn make_map(code: &[u8]) -> HashMap { pc += 1; } + + map.shrink_to_fit(); + map } @@ -97,25 +104,28 @@ pub struct Instruction<'a> { /// Immediate data following the opcode. pub immediate: &'a [u8], /// Program counter of the opcode. - pub pc: usize, + pub pc: u32, } /// Decodes raw opcode bytes into [`Instruction`]s. pub fn decode_instructions(code: &[u8]) -> Result>> { - let mut pc = 0; + 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 immediate_size = op.map(|op| immediate_size(op, &code[pc + 1..])).unwrap_or(0) as usize; + pc += 1; + let immediate_size = op.map(|op| immediate_size(op, &code[pc..])).unwrap_or(0) as usize; - if pc + 1 + immediate_size > code.len() { + if pc + immediate_size > code.len() { eyre::bail!("incomplete sequence of bytecode"); } - steps.push(Instruction { op, pc, immediate: &code[pc + 1..pc + 1 + immediate_size] }); + steps.push(Instruction { op, pc: pc as u32, immediate: &code[pc..pc + immediate_size] }); - pc += 1 + immediate_size; + pc += immediate_size; } Ok(steps) diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index a602ab97a0..a1e7764ab4 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -51,7 +51,9 @@ pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { } /// Simulates `console.log` invocation. - fn console_log(&mut self, _input: String) {} + fn console_log(&mut self, msg: &str) { + let _ = msg; + } /// Returns `true` if the current network is Odyssey. fn is_odyssey(&self) -> bool { diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 4c13369b6a..d135cceb90 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -134,7 +134,7 @@ impl EvmOpts { ) .await .wrap_err_with(|| { - let mut msg = "Could not instantiate forked environment".to_string(); + 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(); diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index f8cc746c5a..8937466803 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -12,7 +12,7 @@ use std::sync::Arc; #[derive(Clone, Debug)] pub struct ContractVisitor<'a> { /// The source ID of the contract. - source_id: usize, + source_id: u32, /// The source code that contains the AST being walked. source: &'a str, @@ -20,7 +20,7 @@ pub struct ContractVisitor<'a> { contract_name: &'a Arc, /// The current branch ID - branch_id: usize, + branch_id: u32, /// Stores the last line we put in the items collection to ensure we don't push duplicate lines last_line: u32, @@ -30,7 +30,14 @@ pub struct ContractVisitor<'a> { impl<'a> ContractVisitor<'a> { pub fn new(source_id: usize, source: &'a str, contract_name: &'a Arc) -> Self { - Self { source_id, source, contract_name, branch_id: 0, last_line: 0, items: Vec::new() } + Self { + source_id: source_id.try_into().expect("too many sources"), + source, + contract_name, + branch_id: 0, + last_line: 0, + items: Vec::new(), + } } pub fn visit_contract(&mut self, node: &Node) -> eyre::Result<()> { @@ -484,7 +491,7 @@ impl<'a> ContractVisitor<'a> { 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, + source_id: self.source_id as usize, contract_name: self.contract_name.clone(), bytes, lines, @@ -508,25 +515,16 @@ fn has_statements(node: &Node) -> bool { } } -/// [`SourceAnalyzer`] result type. -#[derive(Debug)] +/// Coverage source analysis. +#[derive(Clone, Debug, Default)] pub struct SourceAnalysis { - /// A collection of coverage items. - pub items: Vec, -} - -/// Analyzes a set of sources to find coverage items. -#[derive(Debug)] -pub struct SourceAnalyzer<'a> { - sources: &'a SourceFiles<'a>, + /// All the coverage items. + all_items: Vec, + /// Source ID to `(offset, len)` into `all_items`. + map: Vec<(u32, u32)>, } -impl<'a> SourceAnalyzer<'a> { - /// Creates a new source analyzer. - pub fn new(data: &'a SourceFiles<'a>) -> Self { - Self { sources: data } - } - +impl SourceAnalysis { /// Analyzes contracts in the sources held by the source analyzer. /// /// Coverage items are found by: @@ -540,13 +538,12 @@ impl<'a> SourceAnalyzer<'a> { /// 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 analyze(&self) -> eyre::Result { - let items = self - .sources + pub fn new(data: &SourceFiles<'_>) -> eyre::Result { + let mut sourced_items = data .sources .par_iter() .flat_map_iter(|(&source_id, SourceFile { source, ast })| { - ast.nodes.iter().map(move |node| { + let items = ast.nodes.iter().map(move |node| { if !matches!(node.node_type, NodeType::ContractDefinition) { return Ok(vec![]); } @@ -579,10 +576,61 @@ impl<'a> SourceAnalyzer<'a> { } Ok(items) - }) + }); + items.map(move |items| items.map(|items| (source_id, items))) }) - .collect::>>>()?; - Ok(SourceAnalysis { items: items.concat() }) + .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]; + for (idx, items) in sourced_items { + // Assumes that all `idx` items are consecutive, guaranteed by the sort above. + if map[idx].0 == u32::MAX { + map[idx].0 = all_items.len() as u32; + } + map[idx].1 += items.len() as u32; + all_items.extend(items); + } + + Ok(Self { all_items, map }) + } + + /// Returns all the coverage items. + pub fn all_items(&self) -> &[CoverageItem] { + &self.all_items + } + + /// Returns all the mutable coverage items. + pub fn all_items_mut(&mut self) -> &mut Vec { + &mut self.all_items + } + + /// Returns an iterator over the coverage items and their IDs for the given source. + pub fn items_for_source_enumerated( + &self, + source_id: u32, + ) -> impl Iterator { + let (base_id, items) = self.items_for_source(source_id); + items.iter().enumerate().map(move |(idx, item)| (base_id + idx as u32, item)) + } + + /// Returns the base item ID and all the coverage items for the given source. + pub fn items_for_source(&self, source_id: u32) -> (u32, &[CoverageItem]) { + let (mut offset, len) = self.map.get(source_id as usize).copied().unwrap_or_default(); + if offset == u32::MAX { + offset = 0; + } + (offset, &self.all_items[offset as usize..][..len as usize]) + } + + /// Returns the coverage item for the given item ID. + #[inline] + pub fn get(&self, item_id: u32) -> Option<&CoverageItem> { + self.all_items.get(item_id as usize) } } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index ee723d95cc..3d46065518 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -1,5 +1,6 @@ -use super::{CoverageItem, CoverageItemKind, ItemAnchor, SourceLocation}; -use alloy_primitives::map::{DefaultHashBuilder, HashMap, HashSet}; +use super::{CoverageItemKind, ItemAnchor, SourceLocation}; +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::utils::IcPcMap; @@ -10,49 +11,29 @@ pub fn find_anchors( bytecode: &[u8], source_map: &SourceMap, ic_pc_map: &IcPcMap, - items: &[CoverageItem], - items_by_source_id: &HashMap>, + analysis: &SourceAnalysis, ) -> Vec { - let mut seen = HashSet::with_hasher(DefaultHashBuilder::default()); + let mut seen_sources = FxHashSet::default(); source_map .iter() - .filter_map(|element| items_by_source_id.get(&(element.index()? as usize))) - .flatten() - .filter_map(|&item_id| { - if !seen.insert(item_id) { - return None; - } - - let item = &items[item_id]; - let find_anchor_by_first_opcode = |item: &CoverageItem| match find_anchor_simple( - source_map, ic_pc_map, item_id, &item.loc, - ) { - Ok(anchor) => Some(anchor), - Err(e) => { - warn!("Could not find anchor for item {item}: {e}"); - None - } - }; + .filter_map(|element| element.index()) + .filter(|&source| seen_sources.insert(source)) + .flat_map(|source| analysis.items_for_source_enumerated(source)) + .filter_map(|(item_id, item)| { match item.kind { - CoverageItemKind::Branch { path_id, is_first_opcode, .. } => { - if is_first_opcode { - find_anchor_by_first_opcode(item) - } else { - match find_anchor_branch(bytecode, source_map, item_id, &item.loc) { - Ok(anchors) => match path_id { - 0 => Some(anchors.0), - 1 => Some(anchors.1), - _ => panic!("Too many paths for branch"), - }, - Err(e) => { - warn!("Could not find anchor for item {item}: {e}"); - None - } + CoverageItemKind::Branch { path_id, is_first_opcode: false, .. } => { + find_anchor_branch(bytecode, source_map, item_id, &item.loc).map(|anchors| { + match path_id { + 0 => anchors.0, + 1 => anchors.1, + _ => panic!("too many path IDs for branch"), } - } + }) } - _ => find_anchor_by_first_opcode(item), + _ => find_anchor_simple(source_map, ic_pc_map, item_id, &item.loc), } + .inspect_err(|err| warn!(%item, %err, "could not find anchor")) + .ok() }) .collect() } @@ -61,7 +42,7 @@ pub fn find_anchors( pub fn find_anchor_simple( source_map: &SourceMap, ic_pc_map: &IcPcMap, - item_id: usize, + item_id: u32, loc: &SourceLocation, ) -> eyre::Result { let instruction = @@ -70,7 +51,7 @@ pub fn find_anchor_simple( )?; Ok(ItemAnchor { - instruction: ic_pc_map.get(instruction).ok_or_else(|| { + instruction: ic_pc_map.get(instruction as u32).ok_or_else(|| { eyre::eyre!("We found an anchor, but we can't translate it to a program counter") })?, item_id, @@ -108,12 +89,9 @@ pub fn find_anchor_simple( pub fn find_anchor_branch( bytecode: &[u8], source_map: &SourceMap, - item_id: usize, + item_id: u32, loc: &SourceLocation, ) -> eyre::Result<(ItemAnchor, ItemAnchor)> { - // NOTE(onbjerg): We use `SpecId::LATEST` here since it does not matter; the only difference - // is the gas cost. - let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None; let mut pc = 0; let mut cumulative_push_size = 0; @@ -151,12 +129,12 @@ pub fn find_anchor_branch( 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 = usize::try_from(pc_jump).expect("PC is too big"); + let pc_jump = u32::try_from(pc_jump).expect("PC is too big"); anchors = Some(( ItemAnchor { item_id, // The first branch is the opcode directly after JUMPI - instruction: pc + 2, + instruction: (pc + 2) as u32, }, ItemAnchor { item_id, instruction: pc_jump }, )); @@ -171,7 +149,7 @@ pub fn find_anchor_branch( /// Calculates whether `element` is within the range of the target `location`. fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> bool { // Source IDs must match. - let source_ids_match = element.index().is_some_and(|a| a as usize == location.source_id); + let source_ids_match = element.index_i32() == location.source_id as i32; if !source_ids_match { return false; } diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index bc3a40e563..6a6c50b093 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -38,7 +38,7 @@ impl Inspector for CoverageCollector { #[inline] fn step(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { let map = self.get_or_insert_map(interpreter); - map.hit(interpreter.program_counter()); + map.hit(interpreter.program_counter() as u32); } } diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 52ec329add..793e0ee566 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -12,6 +12,7 @@ use alloy_primitives::{ map::{B256HashMap, HashMap}, Bytes, }; +use analysis::SourceAnalysis; use eyre::Result; use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; @@ -41,7 +42,7 @@ pub struct CoverageReport { /// A map of source paths to source IDs. pub source_paths_to_ids: HashMap<(Version, PathBuf), usize>, /// All coverage items for the codebase, keyed by the compiler version. - pub items: HashMap>, + pub analyses: HashMap, /// All item anchors for the codebase, keyed by their contract ID. pub anchors: HashMap, Vec)>, /// All the bytecode hits for the codebase. @@ -70,9 +71,9 @@ impl CoverageReport { self.source_maps.extend(source_maps); } - /// Add coverage items to this report. - pub fn add_items(&mut self, version: Version, items: impl IntoIterator) { - self.items.entry(version).or_default().extend(items); + /// Add a [`SourceAnalysis`] to this report. + pub fn add_analysis(&mut self, version: Version, analysis: SourceAnalysis) { + self.analyses.insert(version, analysis); } /// Add anchors to this report. @@ -98,8 +99,8 @@ impl CoverageReport { mut f: impl FnMut(&mut T, &'a CoverageItem), ) -> impl Iterator { let mut by_file: BTreeMap<&Path, T> = BTreeMap::new(); - for (version, items) in &self.items { - for item in items { + for (version, items) in &self.analyses { + for item in items.all_items() { let key = (version.clone(), item.loc.source_id); let Some(path) = self.source_paths.get(&key) else { continue }; f(by_file.entry(path).or_default(), item); @@ -119,41 +120,42 @@ impl CoverageReport { hit_map: &HitMap, is_deployed_code: bool, ) -> Result<()> { - // Add bytecode level hits + // Add bytecode level hits. self.bytecode_hits .entry(contract_id.clone()) .and_modify(|m| m.merge(hit_map)) .or_insert_with(|| hit_map.clone()); - // Add source level hits + // Add source level hits. if let Some(anchors) = self.anchors.get(contract_id) { let anchors = if is_deployed_code { &anchors.1 } else { &anchors.0 }; for anchor in anchors { if let Some(hits) = hit_map.get(anchor.instruction) { - self.items + self.analyses .get_mut(&contract_id.version) - .and_then(|items| items.get_mut(anchor.item_id)) + .and_then(|items| items.all_items_mut().get_mut(anchor.item_id as usize)) .expect("Anchor refers to non-existent coverage item") .hits += hits.get(); } } } + Ok(()) } - /// Removes all the coverage items that should be ignored by the filter. + /// Retains all the coverage items specified by `predicate`. /// /// This function should only be called after all the sources were used, otherwise, the output /// will be missing the ones that are dependent on them. - pub fn filter_out_ignored_sources(&mut self, filter: impl Fn(&Path) -> bool) { - self.items.retain(|version, items| { - items.retain(|item| { + pub fn retain_sources(&mut self, mut predicate: impl FnMut(&Path) -> bool) { + self.analyses.retain(|version, analysis| { + analysis.all_items_mut().retain(|item| { self.source_paths .get(&(version.clone(), item.loc.source_id)) - .map(|path| filter(path)) + .map(|path| predicate(path)) .unwrap_or(false) }); - !items.is_empty() + !analysis.all_items().is_empty() }); } } @@ -225,20 +227,20 @@ impl HitMap { /// Returns the number of hits for the given program counter. #[inline] - pub fn get(&self, pc: usize) -> Option { - NonZeroU32::new(self.hits.get(&Self::cvt_pc(pc)).copied().unwrap_or(0)) + pub fn get(&self, pc: u32) -> Option { + NonZeroU32::new(self.hits.get(&pc).copied().unwrap_or(0)) } /// Increase the hit counter by 1 for the given program counter. #[inline] - pub fn hit(&mut self, pc: usize) { + pub fn hit(&mut self, pc: u32) { self.hits(pc, 1) } /// Increase the hit counter by `hits` for the given program counter. #[inline] - pub fn hits(&mut self, pc: usize, hits: u32) { - *self.hits.entry(Self::cvt_pc(pc)).or_default() += hits; + pub fn hits(&mut self, pc: u32, hits: u32) { + *self.hits.entry(pc).or_default() += hits; } /// Merge another hitmap into this, assuming the bytecode is consistent @@ -251,8 +253,8 @@ impl HitMap { /// Returns an iterator over all the program counters and their hit counts. #[inline] - pub fn iter(&self) -> impl Iterator + '_ { - self.hits.iter().map(|(&pc, &hits)| (pc as usize, hits)) + pub fn iter(&self) -> impl Iterator + '_ { + self.hits.iter().map(|(&pc, &hits)| (pc, hits)) } /// Returns the number of program counters hit in the hitmap. @@ -266,11 +268,6 @@ impl HitMap { pub fn is_empty(&self) -> bool { self.hits.is_empty() } - - #[inline] - fn cvt_pc(pc: usize) -> u32 { - pc.try_into().expect("4GiB bytecode") - } } /// A unique identifier for a contract @@ -294,10 +291,10 @@ impl Display for ContractId { /// An item anchor describes what instruction marks a [CoverageItem] as covered. #[derive(Clone, Debug)] pub struct ItemAnchor { - /// The program counter for the opcode of this anchor - pub instruction: usize, - /// The item ID this anchor points to - pub item_id: usize, + /// The program counter for the opcode of this anchor. + pub instruction: u32, + /// The item ID this anchor points to. + pub item_id: u32, } impl Display for ItemAnchor { @@ -318,11 +315,11 @@ pub enum CoverageItemKind { /// /// There may be multiple items with the same branch ID - they belong to the same branch, /// but represent different paths. - branch_id: usize, + branch_id: u32, /// The path ID for this branch. /// /// The first path has ID 0, the next ID 1, and so on. - path_id: usize, + path_id: u32, /// If true, then the branch anchor is the first opcode within the branch source range. is_first_opcode: bool, }, diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index ac466adbc9..b3b2d10d2c 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -8,8 +8,10 @@ use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::InvariantConfig; use foundry_evm_core::{ - abi::HARDHAT_CONSOLE_ADDRESS, - constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, MAGIC_ASSUME, TEST_TIMEOUT}, + constants::{ + CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, + TEST_TIMEOUT, + }, precompiles::PRECOMPILES, }; use foundry_evm_fuzz::{ @@ -346,7 +348,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("Revert occurred.")) + return Err(TestCaseError::fail("call reverted")) } while current_run.depth < self.config.depth { @@ -360,7 +362,7 @@ impl<'a> InvariantExecutor<'a> { } let tx = current_run.inputs.last().ok_or_else(|| { - TestCaseError::fail("No input generated to call fuzzed target.") + TestCaseError::fail("no input generated to called fuzz target") })?; // Execute call from the randomly generated sequence without committing state. @@ -373,9 +375,7 @@ impl<'a> InvariantExecutor<'a> { tx.call_details.calldata.clone(), U256::ZERO, ) - .map_err(|e| { - TestCaseError::fail(format!("Could not make raw evm call: {e}")) - })?; + .map_err(|e| TestCaseError::fail(e.to_string()))?; let discarded = call_result.result.as_ref() == MAGIC_ASSUME; if self.config.show_metrics { @@ -392,13 +392,21 @@ impl<'a> InvariantExecutor<'a> { invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects( self.config.max_assume_rejects, )); - return Err(TestCaseError::fail("Max number of vm.assume rejects reached.")) + return Err(TestCaseError::fail( + "reached maximum number of `vm.assume` rejects", + )); } } else { // Commit executed call result. current_run.executor.commit(&mut call_result); // Collect data for fuzzing from the state changeset. + // This step updates the state dictionary and therefore invalidates the + // ValueTree in use by the current run. This manifestsitself in proptest + // observing a different input case than what it was called with, and creates + // inconsistencies whenever proptest tries to use the input case after test + // execution. + // See . let mut state_changeset = call_result.state_changeset.clone(); if !call_result.reverted { collect_data( @@ -444,7 +452,7 @@ impl<'a> InvariantExecutor<'a> { } // If test cannot continue then stop current run and exit test suite. if !result.can_continue { - return Err(TestCaseError::fail("Test cannot continue.")) + return Err(TestCaseError::fail("test cannot continue")) } invariant_test.set_last_call_results(result.call_result); @@ -766,8 +774,7 @@ 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((_, contract)) = - self.project_contracts.find_by_name_or_identifier(identifier)? + if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier) { combined // Check if there's an entry for the given key in the 'combined' map. @@ -775,12 +782,10 @@ impl<'a> InvariantExecutor<'a> { // If the entry exists, extends its ABI with the function list. .and_modify(|entry| { // Extend the ABI's function list with the new functions. - entry.abi.functions.extend(contract.abi.functions.clone()); + entry.abi.functions.extend(abi.functions.clone()); }) // Otherwise insert it into the map. - .or_insert_with(|| { - TargetedContract::new(identifier.to_string(), contract.abi.clone()) - }); + .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi)); } } } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 5db557a16c..318b08ce18 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -768,10 +768,10 @@ pub enum EvmError { #[error(transparent)] Abi(#[from] alloy_dyn_abi::Error), /// Error caused which occurred due to calling the `skip` cheatcode. - #[error("{_0}")] + #[error("{0}")] Skip(SkipReason), /// Any other error. - #[error("{}", foundry_common::errors::display_chain(.0))] + #[error("{0}")] Eyre( #[from] #[source] diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 4abfdcb4e3..4c22a5a012 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,10 +1,7 @@ -use alloy_primitives::{Bytes, Log}; +use alloy_primitives::Log; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; use foundry_common::{fmt::ConsoleFmt, ErrorExt}; -use foundry_evm_core::{ - abi::{patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS}, - InspectorExt, -}; +use foundry_evm_core::{abi::console, constants::HARDHAT_CONSOLE_ADDRESS, InspectorExt}; use revm::{ interpreter::{ CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, @@ -14,28 +11,31 @@ use revm::{ /// An inspector that collects logs during execution. /// -/// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style logs. +/// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs. #[derive(Clone, Debug, Default)] pub struct LogCollector { - /// The collected logs. Includes both `LOG` opcodes and Hardhat-style logs. + /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs. pub logs: Vec, } impl LogCollector { - fn hardhat_log(&mut self, mut input: Vec) -> (InstructionResult, Bytes) { - // Patch the Hardhat-style selector (`uint` instead of `uint256`) - patch_hh_console_selector(&mut input); - - // Decode the call - let decoded = match HardhatConsole::HardhatConsoleCalls::abi_decode(&input, false) { - Ok(inner) => inner, - Err(err) => return (InstructionResult::Revert, err.abi_encode_revert()), - }; - - // Convert the decoded call to a DS `log(string)` event - self.logs.push(convert_hh_log_to_event(decoded)); + #[cold] + fn do_hardhat_log(&mut self, inputs: &CallInputs) -> Option { + if let Err(err) = self.hardhat_log(&inputs.input) { + 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(), + }) + } + None + } - (InstructionResult::Continue, Bytes::new()) + fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { + let decoded = console::hh::ConsoleCalls::abi_decode(data, false)?; + self.logs.push(hh_to_ds(&decoded)); + Ok(()) } } @@ -51,40 +51,30 @@ impl Inspector for LogCollector { inputs: &mut CallInputs, ) -> Option { if inputs.target_address == HARDHAT_CONSOLE_ADDRESS { - let (res, out) = self.hardhat_log(inputs.input.to_vec()); - if res != InstructionResult::Continue { - return Some(CallOutcome { - result: InterpreterResult { - result: res, - output: out, - gas: Gas::new(inputs.gas_limit), - }, - memory_offset: inputs.return_memory_offset.clone(), - }); - } + return self.do_hardhat_log(inputs); } - None } } impl InspectorExt for LogCollector { - fn console_log(&mut self, input: String) { - self.logs.push(Log::new_unchecked( - HARDHAT_CONSOLE_ADDRESS, - vec![Console::log::SIGNATURE_HASH], - input.abi_encode().into(), - )); + fn console_log(&mut self, msg: &str) { + self.logs.push(new_console_log(msg)); } } -/// Converts a call to Hardhat's `console.log` to a DSTest `log(string)` event. -fn convert_hh_log_to_event(call: HardhatConsole::HardhatConsoleCalls) -> Log { +/// 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 fmt = call.fmt(Default::default()); + 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( HARDHAT_CONSOLE_ADDRESS, - vec![Console::log::SIGNATURE_HASH], - fmt.abi_encode().into(), + vec![console::ds::log::SIGNATURE_HASH], + msg.abi_encode().into(), ) } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 3e7680ecb4..d70b7fcbfd 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1041,9 +1041,9 @@ impl InspectorExt for InspectorStackRefMut<'_> { false } - fn console_log(&mut self, input: String) { + fn console_log(&mut self, msg: &str) { call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log( - inspector, input + inspector, msg )); } diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index c17376bfb4..5fefefa853 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -29,6 +29,7 @@ alloy-primitives = { workspace = true, features = [ "getrandom", "arbitrary", "rlp", + "map-indexmap", ] } revm = { workspace = true, features = [ "std", @@ -40,7 +41,7 @@ revm = { workspace = true, features = [ "arbitrary", ] } -eyre .workspace = true +eyre.workspace = true itertools.workspace = true parking_lot.workspace = true proptest.workspace = true @@ -48,5 +49,3 @@ rand.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true -indexmap.workspace = true -ahash.workspace = true diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 7b8ee0e9bf..67f0144af6 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -2,6 +2,7 @@ use super::state::EvmFuzzState; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{Address, B256, I256, U256}; use proptest::prelude::*; +use rand::{rngs::StdRng, SeedableRng}; /// The max length of arrays we fuzz for is 256. const MAX_ARRAY_LEN: usize = 256; @@ -152,20 +153,30 @@ pub fn fuzz_param_from_state( DynSolType::Address => { let deployed_libs = state.deployed_libs.clone(); value() - .prop_filter_map("filter address fuzzed from state", move |value| { - let fuzzed_addr = Address::from_word(value); - // Do not use addresses of deployed libraries as fuzz input. - // See . + .prop_map(move |value| { + let mut fuzzed_addr = Address::from_word(value); if !deployed_libs.contains(&fuzzed_addr) { if no_zksync_reserved_addresses { - Some(DynSolValue::Address(foundry_zksync_core::to_safe_address( - fuzzed_addr, - ))) + DynSolValue::Address(foundry_zksync_core::to_safe_address(fuzzed_addr)) } else { - Some(DynSolValue::Address(fuzzed_addr)) + DynSolValue::Address(fuzzed_addr) } } else { - None + let mut rng = StdRng::seed_from_u64(0x1337); // use deterministic rng + + // Do not use addresses of deployed libraries as fuzz input, instead return + // a deterministically random address. We cannot filter out this value (via + // `prop_filter_map`) as proptest can invoke this closure after test + // execution, and returning a `None` will cause it to panic. + // See and . + loop { + fuzzed_addr.randomize_with(&mut rng); + if !deployed_libs.contains(&fuzzed_addr) { + break; + } + } + + DynSolValue::Address(fuzzed_addr) } }) .boxed() diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index e85328bee9..31a4119910 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -1,10 +1,12 @@ use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts}; use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt}; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{map::HashMap, Address, Bytes, Log, B256, U256}; +use alloy_primitives::{ + map::{AddressIndexSet, B256IndexSet, HashMap}, + Address, Bytes, Log, B256, U256, +}; use foundry_config::FuzzDictionaryConfig; use foundry_evm_core::utils::StateChangeset; -use indexmap::IndexSet; use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; use revm::{ db::{CacheDB, DatabaseRef, DbAccount}, @@ -13,8 +15,6 @@ use revm::{ }; use std::{collections::BTreeMap, fmt, sync::Arc}; -type AIndexSet = IndexSet>; - /// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). /// /// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized @@ -99,9 +99,9 @@ impl EvmFuzzState { #[derive(Default)] pub struct FuzzDictionary { /// Collected state values. - state_values: AIndexSet, + state_values: B256IndexSet, /// Addresses that already had their PUSH bytes collected. - addresses: AIndexSet
, + addresses: AddressIndexSet, /// Configuration for the dictionary. config: FuzzDictionaryConfig, /// Number of state values initially collected from db. @@ -111,7 +111,7 @@ pub struct FuzzDictionary { /// 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. - sample_values: HashMap>, + sample_values: HashMap, /// Avoid addresses less than 2^16 as they are reserved in zkSync space. no_zksync_reserved_addresses: bool, @@ -349,7 +349,7 @@ impl FuzzDictionary { } } - pub fn values(&self) -> &AIndexSet { + pub fn values(&self) -> &B256IndexSet { &self.state_values } @@ -362,12 +362,12 @@ impl FuzzDictionary { } #[inline] - pub fn samples(&self, param_type: &DynSolType) -> Option<&AIndexSet> { + pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> { self.sample_values.get(param_type) } #[inline] - pub fn addresses(&self) -> &AIndexSet
{ + pub fn addresses(&self) -> &AddressIndexSet { &self.addresses } diff --git a/crates/evm/traces/src/debug/mod.rs b/crates/evm/traces/src/debug/mod.rs index a56f4ab2bf..1f3fb0b2fa 100644 --- a/crates/evm/traces/src/debug/mod.rs +++ b/crates/evm/traces/src/debug/mod.rs @@ -80,7 +80,7 @@ impl<'a> DebugStepsWalker<'a> { fn src_map(&self, step: usize) -> Option<(SourceElement, &SourceData)> { self.sources.find_source_mapping( self.contract_name, - self.node.trace.steps[step].pc, + self.node.trace.steps[step].pc as u32, self.node.trace.kind.is_any_create(), ) } diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index 5c0caa5793..d01c074394 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -245,7 +245,7 @@ impl ContractSources { pub fn find_source_mapping( &self, contract_name: &str, - pc: usize, + pc: u32, init_code: bool, ) -> Option<(SourceElement, &SourceData)> { self.get_sources(contract_name)?.find_map(|(artifact, source)| { @@ -265,10 +265,10 @@ impl ContractSources { }?; let ic = pc_ic_map.get(pc)?; - source_map.get(ic)? + source_map.get(ic as usize) } else { - source_map.get(pc)? - }; + source_map.get(pc as usize) + }?; // if the source element has an index, find the sourcemap for that index let res = source_element .index() diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 38dddf8021..9e7bcf2824 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -17,8 +17,11 @@ use foundry_common::{ }; use foundry_config::zksync::ZKSYNC_ARTIFACTS_DIR; use foundry_evm_core::{ - abi::{Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS, HARDHAT_CONSOLE_SELECTOR_PATCHES}, - constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, + abi::console, + constants::{ + CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, + TEST_CONTRACT_ADDRESS, + }, decode::RevertDecoder, precompiles::{ BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, @@ -158,23 +161,6 @@ impl CallTraceDecoder { } fn init() -> Self { - /// All functions from the Hardhat console ABI. - /// - /// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. - fn hh_funcs() -> impl Iterator { - let functions = HardhatConsole::abi::functions(); - let mut functions: Vec<_> = - functions.into_values().flatten().map(|func| (func.selector(), func)).collect(); - let len = functions.len(); - // `functions` is the list of all patched functions; duplicate the unpatched ones - for (unpatched, patched) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { - if let Some((_, func)) = functions[..len].iter().find(|(sel, _)| sel == patched) { - functions.push((unpatched.into(), func.clone())); - } - } - functions.into_iter() - } - Self { contracts: Default::default(), labels: HashMap::from_iter([ @@ -197,16 +183,13 @@ impl CallTraceDecoder { receive_contracts: Default::default(), fallback_contracts: Default::default(), - functions: hh_funcs() - .chain( - Vm::abi::functions() - .into_values() - .flatten() - .map(|func| (func.selector(), func)), - ) - .map(|(selector, func)| (selector, vec![func])) + functions: console::hh::abi::functions() + .into_values() + .chain(Vm::abi::functions().into_values()) + .flatten() + .map(|func| (func.selector(), vec![func])) .collect(), - events: Console::abi::events() + events: console::ds::abi::events() .into_values() .flatten() .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index f88efbb1b1..0d22352ca9 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -201,12 +201,22 @@ pub fn render_trace_arena_inner( } let mut w = TraceWriter::new(Vec::::new()) + .color_cheatcodes(true) + .use_colors(convert_color_choice(shell::color_choice())) .write_bytecodes(with_bytecodes) .with_storage_changes(with_storage_changes); w.write_arena(&arena.resolve_arena()).expect("Failed to write traces"); String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } +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, + shell::ColorChoice::Never => revm_inspectors::ColorChoice::Never, + } +} + /// Specifies the kind of trace. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TraceKind { diff --git a/crates/fmt/README.md b/crates/fmt/README.md index 11e0c5ea30..1fc2712ad6 100644 --- a/crates/fmt/README.md +++ b/crates/fmt/README.md @@ -1,6 +1,7 @@ # Formatter (`fmt`) -Solidity formatter that respects (some parts of) the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and +Solidity formatter that respects (some parts of) +the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and is tested on the [Prettier Solidity Plugin](https://github.com/prettier-solidity/prettier-plugin-solidity) cases. ## Architecture @@ -17,13 +18,19 @@ and works as following: 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. +1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call + corresponding `Formatter`'s callback function. ### Output -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. +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. -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. +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. ### Comments @@ -107,17 +114,22 @@ event Greet(string indexed name); 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` | -| func_attrs_with_params_multiline | true | If function parameters are multiline then always put the function attributes on separate lines | -| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | -| number_underscore | preserve | Style of underscores in number literals. Available options: `remove`, `thousands`, `preserve` | - -TODO: update ^ +| 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 @@ -128,7 +140,8 @@ The formatter can be disabled on specific lines by adding a comment `// forgefmt 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: +Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` +instead: ```solidity uint x = 100; // forgefmt: disable-line @@ -136,7 +149,8 @@ uint x = 100; // forgefmt: disable-line ### 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: +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: ```solidity // forgefmt: disable-start @@ -147,15 +161,19 @@ uint y = 101; ### Testing -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. +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. -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: +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: ```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 `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the +following process: 1. Preparse comments with config values 2. Parse and compare the AST for source & expected files. @@ -201,4 +219,4 @@ Guidelines for contributing to `forge fmt`: 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 + - If it's a new config value, tests covering **all** available options \ No newline at end of file diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 5bdb6178f7..7aa53305b7 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -17,13 +17,6 @@ workspace = true name = "forge" path = "bin/main.rs" -[build-dependencies] -vergen = { workspace = true, default-features = false, features = [ - "build", - "git", - "gitcl", -] } - [dependencies] # lib @@ -101,7 +94,7 @@ toml_edit = "0.22" watchexec = "5.0" watchexec-events = "4.0" watchexec-signals = "4.0" -clearscreen = "3.0" +clearscreen = "4.0" evm-disassembler.workspace = true rustls = { version = "0.23", features = ["ring"] } diff --git a/crates/forge/README.md b/crates/forge/README.md deleted file mode 100644 index a40a852db5..0000000000 --- a/crates/forge/README.md +++ /dev/null @@ -1,446 +0,0 @@ -# `forge` - -Forge is a fast and flexible Ethereum testing framework, inspired by -[Dapp](https://github.com/dapphub/dapptools/tree/master/src/dapp). - -If you are looking into how to consume the software as an end user, check the -[CLI README](../cli/README.md). - -For more context on how the package works under the hood, look in the -[code docs](./src/lib.rs). - -**Need help with Forge? Read the [📖 Foundry Book (Forge Guide)][foundry-book-forge-guide] (WIP)!** - -[foundry-book-forge-guide]: https://book.getfoundry.sh/forge/ - -## Why? - -### Write your tests in Solidity to minimize context switching - -Writing tests in Javascript/Typescript while writing your smart contracts in -Solidity can be confusing. Forge lets you write your tests in Solidity, so you -can focus on what matters. - -```solidity -contract Foo { - uint256 public x = 1; - function set(uint256 _x) external { - x = _x; - } - - function double() external { - x = 2 * x; - } -} - -contract FooTest { - Foo foo; - - // The state of the contract gets reset before each - // test is run, with the `setUp()` function being called - // each time after deployment. - function setUp() public { - foo = new Foo(); - } - - // A simple unit test - function testDouble() public { - require(foo.x() == 1); - foo.double(); - require(foo.x() == 2); - } -} -``` - -### Fuzzing: Go beyond unit testing - -When testing smart contracts, fuzzing can uncover edge cases which would be hard -to manually detect with manual unit testing. We support fuzzing natively, where -any test function that takes >0 arguments will be fuzzed, using the -[proptest](https://docs.rs/proptest/1.0.0/proptest/) crate. - -An example of how a fuzzed test would look like can be seen below: - -```solidity -function testDoubleWithFuzzing(uint256 x) public { - foo.set(x); - require(foo.x() == x); - foo.double(); - require(foo.x() == 2 * x); -} -``` - -## Features - -- [ ] test - - [x] Simple unit tests - - [x] Gas costs - - [x] DappTools style test output - - [x] JSON test output - - [x] Matching on regex - - [x] DSTest-style assertions support - - [x] Fuzzing - - [ ] Symbolic execution - - [ ] Coverage - - [x] HEVM-style Solidity cheatcodes - - [ ] Structured tracing with abi decoding - - [ ] Per-line gas profiling - - [x] Forking mode - - [x] Automatic solc selection -- [x] build - - [x] Can read DappTools-style .sol.json artifacts - - [x] Manual remappings - - [x] Automatic remappings - - [x] Multiple compiler versions - - [x] Incremental compilation - - [ ] Can read Hardhat-style artifacts - - [ ] Can read Truffle-style artifacts -- [x] install -- [x] update -- [ ] debug -- [x] CLI Tracing with `RUST_LOG=forge=trace` - -### Gas Report - -Foundry will show you a comprehensive gas report about your contracts. It returns the `min`, `average`, `median` and, `max` gas cost for every function. - -It looks at **all** the tests that make a call to a given function and records the associated gas costs. For example, if something calls a function and it reverts, that's probably the `min` value. Another example is the `max` value that is generated usually during the first call of the function (as it has to initialise storage, variables, etc.) - -Usually, the `median` value is what your users will probably end up paying. `max` and `min` concern edge cases that you might want to explicitly test against, but users will probably never encounter. - -image - -### Cheat codes - -_The below is modified from -[Dapp's README](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#cheat-codes)_ - -We allow modifying blockchain state with "cheat codes". These can be accessed by -calling into a contract at address `0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`, -which implements the following methods: - -- `function warp(uint x) public` Sets the block timestamp to `x`. - -- `function difficulty(uint x) public` Sets the block difficulty to `x`. - -- `function roll(uint x) public` Sets the block number to `x`. - -- `function coinbase(address c) public` Sets the block coinbase to `c`. - -- `function store(address c, bytes32 loc, bytes32 val) public` Sets the slot - `loc` of contract `c` to `val`. - -- `function load(address c, bytes32 loc) public returns (bytes32 val)` Reads the - slot `loc` of contract `c`. - -- `function sign(uint sk, bytes32 digest) public returns (uint8 v, bytes32 r, bytes32 s)` - Signs the `digest` using the private key `sk`. Note that signatures produced - via `hevm.sign` will leak the private key. - -- `function addr(uint sk) public returns (address addr)` Derives an ethereum - address from the private key `sk`. Note that `hevm.addr(0)` will fail with - `BadCheatCode` as `0` is an invalid ECDSA private key. `sk` values above the - secp256k1 curve order, near the max uint256 value will also fail. - -- `function ffi(string[] calldata) external returns (bytes memory)` Executes the - arguments as a command in the system shell and returns stdout. Note that this - cheatcode means test authors can execute arbitrary code on user machines as - part of a call to `forge test`, for this reason all calls to `ffi` will fail - unless the `--ffi` flag is passed. - -- `function deal(address who, uint256 amount)`: Sets an account's balance - -- `function etch(address where, bytes memory what)`: Sets the contract code at - some address contract code - -- `function prank(address sender)`: Performs the next smart contract call as another address (prank just changes msg.sender. Tx still occurs as normal) - -- `function prank(address sender, address origin)`: Performs the next smart contract call setting both `msg.sender` and `tx.origin`. - -- `function startPrank(address sender)`: Performs smart contract calls as another address. The account impersonation lasts until the end of the transaction, or until `stopPrank` is called. - -- `function startPrank(address sender, address origin)`: Performs smart contract calls as another address, while also setting `tx.origin`. The account impersonation lasts until the end of the transaction, or until `stopPrank` is called. - -- `function stopPrank()`: Stop calling smart contracts with the address set at `startPrank` - -- `function expectRevert( expectedError)`: - Tells the evm to expect that the next call reverts with specified error bytes. Valid input types: `bytes`, and `bytes4`. Implicitly, strings get converted to bytes except when shorter than 4, in which case you will need to cast explicitly to `bytes`. -- `function expectEmit(bool,bool,bool,bool) external`: Expects the next emitted event. Params check topic 1, topic 2, topic 3 and data are the same. - -- `function expectEmit(bool,bool,bool,bool,address) external`: Expects the next emitted event. Params check topic 1, topic 2, topic 3 and data are the same. Also checks supplied address against address of originating contract. - -- `function getCode(string calldata) external returns (bytes memory)`: Fetches bytecode from a contract artifact. The parameter can either be in the form `ContractFile.sol` (if the filename and contract name are the same), `ContractFile.sol:ContractName`, or `./path/to/artifact.json`. - -- `function label(address addr, string calldata label) external`: Label an address in test traces. - -- `function assume(bool) external`: When fuzzing, generate new inputs if conditional not met - -- `function setNonce(address account, uint64 nonce) external`: Set nonce for an account, increment only. - -- `function getNonce(address account)`: Get nonce for an account. - -- `function chainId(uint x) public` Sets the block chainid to `x`. - -The below example uses the `warp` cheatcode to override the timestamp & `expectRevert` to expect a specific revert string: - -```solidity -interface Vm { - function warp(uint256 x) external; - function expectRevert(bytes calldata) external; -} - -contract Foo { - function bar(uint256 a) public returns (uint256) { - require(a < 100, "My expected revert string"); - return a; - } -} - -contract MyTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - function testWarp() public { - vm.warp(100); - require(block.timestamp == 100); - } - - function testBarExpectedRevert() public { - vm.expectRevert("My expected revert string"); - // This would fail *if* we didn't expect revert. Since we expect the revert, - // it doesn't, unless the revert string is wrong. - foo.bar(101); - } - - function testFailBar() public { - // this call would revert, causing this test to pass - foo.bar(101); - } -} -``` - -Below is another example using the `expectEmit` cheatcode to check events: - -```solidity -interface Vm { - function expectEmit(bool,bool,bool,bool) external; - function expectEmit(bool,bool,bool,bool,address) external; -} - -contract T is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - event Transfer(address indexed from,address indexed to, uint256 amount); - function testExpectEmit() public { - ExpectEmit emitter = new ExpectEmit(); - // check topic 1, topic 2, and data are the same as the following emitted event - vm.expectEmit(true,true,false,true); - emit Transfer(address(this), address(1337), 1337); - emitter.t(); - } - - function testExpectEmitWithAddress() public { - ExpectEmit emitter = new ExpectEmit(); - // do the same as above and check emitting address - vm.expectEmit(true,true,false,true,address(emitter)); - emit Transfer(address(this), address(1337), 1337); - emitter.t(); - } -} - -contract ExpectEmit { - event Transfer(address indexed from,address indexed to, uint256 amount); - function t() public { - emit Transfer(msg.sender, address(1337), 1337); - } -} -``` - -A full interface for all cheatcodes is here: - -```solidity -interface Hevm { - // Set block.timestamp (newTimestamp) - function warp(uint256) external; - // Set block.height (newHeight) - function roll(uint256) external; - // Set block.basefee (newBasefee) - function fee(uint256) external; - // Set block.coinbase (who) - function coinbase(address) external; - // Loads a storage slot from an address (who, slot) - function load(address,bytes32) external returns (bytes32); - // Stores a value to an address' storage slot, (who, slot, value) - function store(address,bytes32,bytes32) external; - // Signs data, (privateKey, digest) => (v, r, s) - function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); - // Gets address for a given private key, (privateKey) => (address) - function addr(uint256) external returns (address); - // Performs a foreign function call via terminal, (stringInputs) => (result) - function ffi(string[] calldata) external returns (bytes memory); - // Sets the *next* call's msg.sender to be the input address - function prank(address) external; - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called - function startPrank(address) external; - // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input - function prank(address,address) external; - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input - function startPrank(address,address) external; - // Resets subsequent calls' msg.sender to be `address(this)` - function stopPrank() external; - // Sets an address' balance, (who, newBalance) - function deal(address, uint256) external; - // Sets an address' code, (who, newCode) - function etch(address, bytes calldata) external; - // Expects an error on next call - function expectRevert() external; - function expectRevert(bytes calldata) external; - function expectRevert(bytes4) external; - // Record all storage reads and writes - function record() external; - // Gets all accessed reads and write slot from a recording session, for a given address - function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); - // 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) - function expectEmit(bool,bool,bool,bool) external; - // Mocks a call to an address, returning specified data. - // Calldata can either be strict or a partial match, e.g. if you only - // pass a Solidity selector to the expected calldata, then the entire Solidity - // function will be mocked. - function mockCall(address,bytes calldata,bytes calldata) external; - // Mocks a call to an address with a specific msg.value, returning specified data. - // Calldata match takes precedence over msg.value in case of ambiguity. - function mockCall(address,uint256,bytes calldata,bytes calldata) external; - // Reverts a call to an address with specified revert data. - function mockCallRevert(address, bytes calldata, bytes calldata) external; - // Reverts a call to an address with a specific msg.value, with specified revert data. - function mockCallRevert(address, uint256 msgValue, bytes calldata, bytes calldata) external; - // Clears all mocked and reverted mocked calls - function clearMockedCalls() external; - // Expect a call to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata) external; - // Expect given number of calls to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata, uint64) external; - - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other - // memory is written to, the test will fail. - function expectSafeMemory(uint64, uint64) external; - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. - // If any other memory is written to, the test will fail. - function expectSafeMemoryCall(uint64, uint64) external; - // Fetches the contract bytecode from its artifact file - function getCode(string calldata) external returns (bytes memory); - // Label an address in test traces - function label(address addr, string calldata label) external; - // When fuzzing, generate new inputs if conditional not met - function assume(bool) external; - // Set nonce for an account, increment only - function setNonce(address,uint64) external; - // Get nonce for an account - function getNonce(address) external returns(uint64); -} -``` - -### `console.log` - -We support the logging functionality from Hardhat's `console.log`. - -If you are on a hardhat project, `import hardhat/console.sol` should just work if you use `forge test --hh`. - -If no, there is an implementation contract [here](https://raw.githubusercontent.com/NomicFoundation/hardhat/master/packages/hardhat-core/console.sol). We currently recommend that you copy this contract, place it in your `test` folder, and import it into the contract where you wish to use `console.log`, though there should be more streamlined functionality soon. - -Usage follows the same format as [Hardhat](https://hardhat.org/hardhat-network/reference/#console-log): - -```solidity -import "./console.sol"; -... -console.log(someValue); - -``` - -Note: to make logs visible in `stdout`, you must use at least level 2 verbosity. - -```bash -$> forge test -vv -[PASS] test1() (gas: 7683) -... -Logs: - - ... -``` - -## Remappings - -If you are working in a repo with NPM-style imports, like - -```solidity -import "@openzeppelin/contracts/access/Ownable.sol"; -``` - -then you will need to create a `remappings.txt` file at the top level of your project directory, so that Forge knows where to find these dependencies. - -For example, if you have `@openzeppelin` imports, you would - -1. `forge install openzeppelin/openzeppelin-contracts` (this will add the repo to `lib/openzepplin-contracts`) -2. Create a remappings file: `touch remappings.txt` -3. Add this line to `remappings.txt` - -```text -@openzeppelin/=lib/openzeppelin-contracts/ -``` - -## Github Actions CI - -We recommend using the [Github Actions CI setup](https://book.getfoundry.sh/config/continuous-integration) from the [📖 Foundry Book](https://book.getfoundry.sh/index.html). - -## Future Features - -### Dapptools feature parity - -Over the next months, we intend to add the following features which are -available in upstream dapptools: - -1. Stack Traces: Currently we do not provide any debug information when a call - fails. We intend to add a structured printer (something like - [this](https://twitter.com/gakonst/status/1434337110111182848) which will - show all the calls, logs and arguments passed across intermediate smart - contract calls, which should help with debugging. -1. [Invariant Tests](https://github.com/dapphub/dapptools/blob/master/src/dapp/README.md#invariant-testing) -1. [Interactive Debugger](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#interactive-debugger-key-bindings) -1. [Code coverage](https://twitter.com/dapptools/status/1435973810545729536) -1. [Gas snapshots](https://github.com/dapphub/dapptools/pull/850/files) -1. [Symbolic EVM](https://fv.ethereum.org/2020/07/28/symbolic-hevm-release/) - -### Unique features? - -We also intend to add features which are not available in dapptools: - -1. Even faster tests with parallel EVM execution that produces state diffs - instead of modifying the state -1. Improved UX for assertions: - 1. Check revert error or reason on a Solidity call - 1. Check that an event was emitted with expected arguments -1. Support more EVM backends ([revm](https://github.com/bluealloy/revm/), geth's - evm, hevm etc.) & benchmark performance across them -1. Declarative deployment system based on a config file -1. Formatting & Linting (maybe powered by - [Solang](https://github.com/hyperledger-labs/solang)) - 1. `forge fmt`, an automatic code formatter according to standard rules (like - [`prettier-plugin-solidity`](https://github.com/prettier-solidity/prettier-plugin-solidity)) - 1. `forge lint`, a linter + static analyzer, like a combination of - [`solhint`](https://github.com/protofire/solhint) and - [slither](https://github.com/crytic/slither/) -1. Flamegraphs for gas profiling diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index c8763d08c0..2460f4ec51 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -106,7 +106,7 @@ impl BindArgs { let _ = ProjectCompiler::new().compile(&project)?; } - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let artifacts = config.out; let bindings_root = self.bindings.clone().unwrap_or_else(|| artifacts.join("bindings")); diff --git a/crates/forge/bin/cmd/bind_json.rs b/crates/forge/bin/cmd/bind_json.rs index 593380297c..891e019bc5 100644 --- a/crates/forge/bin/cmd/bind_json.rs +++ b/crates/forge/bin/cmd/bind_json.rs @@ -65,7 +65,7 @@ impl BindJsonArgs { /// 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.try_load_config_emit_warnings()?; + let config = self.load_config()?; let project = config.create_project(false, true)?; let target_path = config.root.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); @@ -77,7 +77,7 @@ impl BindJsonArgs { let mut sources = graph // resolve graph into mapping language -> version -> sources .into_sources_by_version(&project)? - .0 + .sources .into_iter() // we are only interested in Solidity sources .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity)) diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 7f10d0cae6..61e8686b34 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -28,16 +28,6 @@ foundry_config::merge_impl_figment_convert!(BuildArgs, build); /// In order to override them in the foundry `Config` they need to be merged into an existing /// `figment::Provider`, like `foundry_config::Config` is. /// -/// # Example -/// -/// ``` -/// use foundry_cli::cmd::forge::build::BuildArgs; -/// use foundry_config::Config; -/// # fn t(args: BuildArgs) { -/// let config = Config::from(&args); -/// # } -/// ``` -/// /// `BuildArgs` implements `figment::Provider` in which all config related fields are serialized and /// then merged into an existing `Config`, effectively overwriting them. /// @@ -79,11 +69,11 @@ impl BuildArgs { // TODO(zk): We cannot return `ProjectCompileOutput` as there's currently no way to return // a common type from solc and zksolc branches. pub fn run(self) -> Result<()> { - let mut config = self.try_load_config_emit_warnings()?; + let mut config = self.load_config()?; if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings - config = self.load_config(); + config = self.load_config()?; } if !config.zksync.should_compile() { @@ -179,9 +169,9 @@ impl BuildArgs { // Use the path arguments or if none where provided the `src`, `test` and `script` // directories as well as the `foundry.toml` configuration file. self.watch.watchexec_config(|| { - let config = Config::from(self); + let config = self.load_config()?; let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME); - [config.src, config.test, config.script, foundry_toml] + Ok([config.src, config.test, config.script, foundry_toml]) }) } } diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index fd502dd7b6..c1e4ce5278 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -7,7 +7,10 @@ use foundry_block_explorers::{ errors::EtherscanError, Client, }; -use foundry_cli::{opts::EtherscanOpts, utils::Git}; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{Git, LoadConfig}, +}; use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ artifacts::{ @@ -96,7 +99,7 @@ impl CloneArgs { self; // step 0. get the chain and api key from the config - let config = Config::from(ðerscan); + 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())?; @@ -548,7 +551,7 @@ fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result Result { - let mut config = Config::load_with_root(root).sanitized(); + let mut config = Config::load_with_root(root)?.sanitized(); config.extra_output.push(ContractOutputSelection::StorageLayout); let project = config.project()?; let compiler = ProjectCompiler::new(); diff --git a/crates/forge/bin/cmd/compiler.rs b/crates/forge/bin/cmd/compiler.rs index f5d62b6714..19e4354ca9 100644 --- a/crates/forge/bin/cmd/compiler.rs +++ b/crates/forge/bin/cmd/compiler.rs @@ -59,11 +59,11 @@ impl ResolveArgs { let Self { root, skip } = self; let root = root.unwrap_or_else(|| PathBuf::from(".")); - let config = Config::load_with_root(&root); + let config = Config::load_with_root(&root)?; let project = config.project()?; let graph = Graph::resolve(&project.paths)?; - let (sources, _) = graph.into_sources_by_version(&project)?; + let sources = graph.into_sources_by_version(&project)?.sources; let mut output: BTreeMap> = BTreeMap::new(); diff --git a/crates/forge/bin/cmd/config.rs b/crates/forge/bin/cmd/config.rs index 42ab29ec26..a85e239134 100644 --- a/crates/forge/bin/cmd/config.rs +++ b/crates/forge/bin/cmd/config.rs @@ -36,7 +36,8 @@ impl ConfigArgs { } let config = self - .try_load_config_unsanitized_emit_warnings()? + .load_config_unsanitized()? + .normalized_optimizer_settings() // we explicitly normalize the version, so mimic the behavior when invoking solc .normalized_evm_version(); diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index ef596e4d4c..fdd01fe357 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -1,10 +1,10 @@ -use super::{install, test::TestArgs}; +use super::{install, test::TestArgs, watch::WatchArgs}; use alloy_primitives::{map::HashMap, Address, Bytes, U256}; use clap::{Parser, ValueEnum, ValueHint}; use eyre::{Context, Result}; use forge::{ coverage::{ - analysis::{SourceAnalysis, SourceAnalyzer, SourceFile, SourceFiles}, + analysis::{SourceAnalysis, SourceFile, SourceFiles}, anchors::find_anchors, BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, DebugReporter, ItemAnchor, LcovReporter, @@ -14,19 +14,18 @@ use forge::{ MultiContractRunnerBuilder, }; use foundry_cli::utils::{self, LoadConfig, STATIC_FUZZ_SEED}; -use foundry_common::{compile::ProjectCompiler, fs}; +use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ artifacts::{ sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage, Source, }, compilers::multi::MultiCompiler, - Artifact, ArtifactId, Project, ProjectCompileOutput, + Artifact, ArtifactId, Project, ProjectCompileOutput, ProjectPathsConfig, }; -use foundry_config::{Config, SolcReq}; +use foundry_config::Config; use rayon::prelude::*; use semver::{Version, VersionReq}; use std::{ - io, path::{Path, PathBuf}, sync::Arc, }; @@ -35,7 +34,7 @@ use std::{ foundry_config::impl_figment_convert!(CoverageArgs, test); /// CLI arguments for `forge coverage`. -#[derive(Clone, Debug, Parser)] +#[derive(Parser)] pub struct CoverageArgs { /// The report type to use for coverage. /// @@ -76,18 +75,22 @@ pub struct CoverageArgs { #[arg(long)] include_libs: bool, + /// The coverage reporters to use. Constructed from the other fields. + #[arg(skip)] + reporters: Vec>, + #[command(flatten)] test: TestArgs, } impl CoverageArgs { - pub async fn run(self) -> Result<()> { - let (mut config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + pub async fn run(mut self) -> Result<()> { + 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 { // need to re-configure here to also catch additional remappings - config = self.load_config(); + config = self.load_config()?; } // Set fuzz seed so coverage reports are deterministic @@ -96,18 +99,47 @@ impl CoverageArgs { // Coverage analysis requires the Solc AST output. config.ast = true; - let (project, output) = self.build(&config)?; + let (paths, output) = { + let (project, output) = self.build(&config)?; + (project.paths, output) + }; + + self.populate_reporters(&paths.root); + sh_println!("Analysing contracts...")?; - let report = self.prepare(&project, &output)?; + let report = self.prepare(&paths, &output)?; sh_println!("Running tests...")?; - self.collect(project, &output, report, Arc::new(config), evm_opts).await + self.collect(&paths.root, &output, report, Arc::new(config), evm_opts).await + } + + fn populate_reporters(&mut self, root: &Path) { + self.reporters = self + .report + .iter() + .map(|report_kind| match report_kind { + CoverageReportKind::Summary => { + Box::::default() as Box + } + CoverageReportKind::Lcov => { + let path = + root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); + Box::new(LcovReporter::new(path, self.lcov_version.clone())) + } + CoverageReportKind::Bytecode => Box::new(BytecodeReporter::new( + root.to_path_buf(), + root.join("bytecode-coverage"), + )), + CoverageReportKind::Debug => Box::new(DebugReporter), + }) + .collect::>(); } /// Builds the project. fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { // Set up the project - let mut project = config.create_project(false, false)?; + let mut project = config.create_project(false, true)?; + if self.ir_minimum { // print warning message sh_warn!("{}", concat!( @@ -118,20 +150,17 @@ impl CoverageArgs { "See more: https://github.com/foundry-rs/foundry/issues/3357", ))?; - // 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 + // 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(); - let version = if let Some(SolcReq::Version(version)) = &config.solc { - version - } else { - // Sanitize settings for solc 0.8.4 if version cannot be detected. - // See . - &Version::new(0, 8, 4) - }; - project.settings.solc.settings.sanitize(version, SolcLanguage::Solidity); + + // 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 { project.settings.solc.optimizer.disable(); project.settings.solc.optimizer.runs = None; @@ -139,6 +168,13 @@ impl CoverageArgs { project.settings.solc.via_ir = None; } + let mut warning = + "optimizer settings have been disabled for accurate coverage reports".to_string(); + if !self.ir_minimum { + warning += ", if you encounter \"stack too deep\" errors, consider using `--ir-minimum` which enables viaIR with minimum optimization resolving most of the errors"; + } + sh_warn!("{warning}")?; + let output = ProjectCompiler::default() .compile(&project)? .with_stripped_file_prefixes(project.root()); @@ -148,16 +184,19 @@ impl CoverageArgs { /// Builds the coverage report. #[instrument(name = "prepare", skip_all)] - fn prepare(&self, project: &Project, output: &ProjectCompileOutput) -> Result { + fn prepare( + &self, + project_paths: &ProjectPathsConfig, + output: &ProjectCompileOutput, + ) -> Result { let mut report = CoverageReport::default(); // Collect source files. - let project_paths = &project.paths; let mut versioned_sources = HashMap::>::default(); for (path, source_file, version) in output.output().sources.sources_with_version() { report.add_source(version.clone(), source_file.id as usize, path.clone()); - // Filter out dependencies + // Filter out dependencies. if !self.include_libs && project_paths.has_library_ancestor(path) { continue; } @@ -179,7 +218,7 @@ impl CoverageArgs { } } - // Get source maps and bytecodes + // Get source maps and bytecodes. let artifacts: Vec = output .artifact_ids() .par_bridge() // This parses source maps, so we want to run it in parallel. @@ -189,53 +228,40 @@ impl CoverageArgs { }) .collect(); - // Add coverage items + // Add coverage items. for (version, sources) in &versioned_sources { - let source_analysis = SourceAnalyzer::new(sources).analyze()?; - - // Build helper mapping used by `find_anchors` - let mut items_by_source_id = HashMap::<_, Vec<_>>::with_capacity_and_hasher( - source_analysis.items.len(), - Default::default(), - ); - - for (item_id, item) in source_analysis.items.iter().enumerate() { - items_by_source_id.entry(item.loc.source_id).or_default().push(item_id); - } - + let source_analysis = SourceAnalysis::new(sources)?; let anchors = artifacts .par_iter() .filter(|artifact| artifact.contract_id.version == *version) .map(|artifact| { - let creation_code_anchors = - artifact.creation.find_anchors(&source_analysis, &items_by_source_id); - let deployed_code_anchors = - artifact.deployed.find_anchors(&source_analysis, &items_by_source_id); + let creation_code_anchors = artifact.creation.find_anchors(&source_analysis); + let deployed_code_anchors = artifact.deployed.find_anchors(&source_analysis); (artifact.contract_id.clone(), (creation_code_anchors, deployed_code_anchors)) }) - .collect::>(); - - report.add_anchors(anchors); - report.add_items(version.clone(), source_analysis.items); + .collect_vec_list(); + report.add_anchors(anchors.into_iter().flatten()); + report.add_analysis(version.clone(), source_analysis); } - report.add_source_maps(artifacts.into_iter().map(|artifact| { - (artifact.contract_id, (artifact.creation.source_map, artifact.deployed.source_map)) - })); + if self.reporters.iter().any(|reporter| reporter.needs_source_maps()) { + report.add_source_maps(artifacts.into_iter().map(|artifact| { + (artifact.contract_id, (artifact.creation.source_map, artifact.deployed.source_map)) + })); + } Ok(report) } /// Runs tests, collects coverage data and generates the final report. async fn collect( - self, - project: Project, + mut self, + root: &Path, output: &ProjectCompileOutput, mut report: CoverageReport, config: Arc, evm_opts: EvmOpts, ) -> Result<()> { - let root = project.paths.root; let verbosity = evm_opts.verbosity; let strategy = utils::get_executor_strategy(&config); @@ -247,13 +273,12 @@ impl CoverageArgs { .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .set_coverage(true) - .build::(&root, output, None, env, evm_opts, strategy)?; + .build::(root, output, None, env, evm_opts, strategy)?; let known_contracts = runner.known_contracts.clone(); let filter = self.test.filter(&config); - let outcome = - self.test.run_tests(runner, config.clone(), verbosity, &filter, output).await?; + let outcome = self.test.run_tests(runner, config, verbosity, &filter, output).await?; outcome.ensure_ok(false)?; @@ -291,35 +316,30 @@ impl CoverageArgs { } } - // Filter out ignored sources from the report - let file_pattern = filter.args().coverage_pattern_inverse.as_ref(); - let file_root = &filter.paths().root; - report.filter_out_ignored_sources(|path: &Path| { - file_pattern.is_none_or(|re| { - !re.is_match(&path.strip_prefix(file_root).unwrap_or(path).to_string_lossy()) - }) - }); + // Filter out ignored sources from the report. + if let Some(not_re) = &filter.args().coverage_pattern_inverse { + let file_root = filter.paths().root.as_path(); + report.retain_sources(|path: &Path| { + let path = path.strip_prefix(file_root).unwrap_or(path); + !not_re.is_match(&path.to_string_lossy()) + }); + } - // Output final report - for report_kind in self.report { - match report_kind { - CoverageReportKind::Summary => CoverageSummaryReporter::default().report(&report), - CoverageReportKind::Lcov => { - let path = - root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); - let mut file = io::BufWriter::new(fs::create_file(path)?); - LcovReporter::new(&mut file, self.lcov_version.clone()).report(&report) - } - CoverageReportKind::Bytecode => { - let destdir = root.join("bytecode-coverage"); - fs::create_dir_all(&destdir)?; - BytecodeReporter::new(root.clone(), destdir).report(&report) - } - CoverageReportKind::Debug => DebugReporter.report(&report), - }?; + // Output final reports. + for reporter in &mut self.reporters { + reporter.report(&report)?; } + Ok(()) } + + pub(crate) fn is_watch(&self) -> bool { + self.test.is_watch() + } + + pub(crate) fn watch(&self) -> &WatchArgs { + &self.test.watch + } } /// Coverage reports to generate. @@ -403,18 +423,8 @@ impl BytecodeData { Self { source_map, bytecode, ic_pc_map } } - pub fn find_anchors( - &self, - source_analysis: &SourceAnalysis, - items_by_source_id: &HashMap>, - ) -> Vec { - find_anchors( - &self.bytecode, - &self.source_map, - &self.ic_pc_map, - &source_analysis.items, - items_by_source_id, - ) + pub fn find_anchors(&self, source_analysis: &SourceAnalysis) -> Vec { + find_anchors(&self.bytecode, &self.source_map, &self.ic_pc_map, source_analysis) } } diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 1ad51950c7..a5662e09c4 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -109,11 +109,11 @@ pub struct CreateArgs { impl CreateArgs { /// Executes the command to create a contract pub async fn run(mut self) -> Result<()> { - let mut config = self.try_load_config_emit_warnings()?; + let mut config = self.load_config()?; // Install missing dependencies. if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings - config = self.load_config(); + config = self.load_config()?; } // Find Project & Compile @@ -258,7 +258,7 @@ impl CreateArgs { // Check config for Etherscan API Keys to avoid preflight check failing if no // ETHERSCAN_API_KEY value set. - let config = verify.load_config_emit_warnings(); + let config = verify.load_config()?; verify.etherscan.key = config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); diff --git a/crates/forge/bin/cmd/create/zksync.rs b/crates/forge/bin/cmd/create/zksync.rs index a57b1d7b72..745a55e8b6 100644 --- a/crates/forge/bin/cmd/create/zksync.rs +++ b/crates/forge/bin/cmd/create/zksync.rs @@ -71,7 +71,7 @@ impl CreateArgs { project.find_contract_path(&self.contract.name)? }; - let config = self.build.try_load_config_emit_warnings()?; + let config = self.build.load_config()?; let zk_project = foundry_config::zksync::config_create_project(&config, config.cache, false)?; let zk_compiler = ProjectCompiler::new().files([target_path.clone()]); @@ -105,7 +105,7 @@ impl CreateArgs { }; // Add arguments to constructor - let config = self.eth.try_load_config_emit_warnings()?; + let config = self.eth.load_config()?; let provider = utils::get_provider_zksync(&config)?; let params = match abi.constructor { Some(ref v) => { diff --git a/crates/forge/bin/cmd/doc/mod.rs b/crates/forge/bin/cmd/doc/mod.rs index 2fa996a04f..73b78618cb 100644 --- a/crates/forge/bin/cmd/doc/mod.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -6,7 +6,7 @@ use forge_doc::{ }; use foundry_cli::opts::GH_REPO_PREFIX_REGEX; use foundry_common::compile::ProjectCompiler; -use foundry_config::{find_project_root, load_config_with_root, Config}; +use foundry_config::{load_config_with_root, Config}; use std::{path::PathBuf, process::Command}; mod server; @@ -139,10 +139,6 @@ impl DocArgs { } pub fn config(&self) -> Result { - let root = match &self.root { - Some(root) => root, - None => &find_project_root(None), - }; - Ok(load_config_with_root(Some(root))) + load_config_with_root(self.root.as_deref()) } } diff --git a/crates/forge/bin/cmd/eip712.rs b/crates/forge/bin/cmd/eip712.rs index 86a6d57c3c..5e61c94e73 100644 --- a/crates/forge/bin/cmd/eip712.rs +++ b/crates/forge/bin/cmd/eip712.rs @@ -24,7 +24,7 @@ pub struct Eip712Args { impl Eip712Args { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let mut project = config.create_project(false, true)?; let target_path = dunce::canonicalize(self.target_path)?; project.update_output_selection(|selection| { diff --git a/crates/forge/bin/cmd/flatten.rs b/crates/forge/bin/cmd/flatten.rs index 3a3fb905e5..5a5e0cdafa 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -40,7 +40,7 @@ impl FlattenArgs { // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build = BuildOpts { project_paths, ..Default::default() }; - let config = build.try_load_config_emit_warnings()?; + let config = build.load_config()?; let project = config.create_project(false, true)?; let target_path = dunce::canonicalize(target_path)?; diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index 137e139e6b..206eb7b960 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -45,7 +45,7 @@ impl_figment_convert_basic!(FmtArgs); impl FmtArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; // Expand ignore globs and canonicalize from the get go let ignored = expand_globs(&config.root, config.fmt.ignore.iter())? diff --git a/crates/forge/bin/cmd/geiger.rs b/crates/forge/bin/cmd/geiger.rs index ace3bbe1af..57dac0eb26 100644 --- a/crates/forge/bin/cmd/geiger.rs +++ b/crates/forge/bin/cmd/geiger.rs @@ -91,7 +91,7 @@ impl GeigerArgs { sh_warn!("`--full` is deprecated as reports are not generated anymore\n")?; } - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let sources = self.sources(&config).wrap_err("Failed to resolve files")?; if config.ffi { diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index 1b9b123b26..fafed869fd 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -130,7 +130,7 @@ impl InitArgs { // write foundry.toml, if it doesn't exist already let dest = root.join(Config::FILE_NAME); - let mut config = Config::load_with_root(&root); + let mut config = Config::load_with_root(&root)?; if !dest.exists() { fs::write(dest, config.clone().into_basic().to_string_pretty()?)?; } diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index d1836c9bcc..42ba370b2e 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,3 +1,4 @@ +use alloy_json_abi::{EventParam, InternalType, JsonAbi, Param}; use alloy_primitives::{hex, keccak256, Address}; use clap::Parser; use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; @@ -17,7 +18,8 @@ use foundry_compilers::{ utils::canonicalize, }; use regex::Regex; -use std::{fmt, sync::LazyLock}; +use serde_json::{Map, Value}; +use std::{collections::BTreeMap, fmt, sync::LazyLock}; /// CLI arguments for `forge inspect`. #[derive(Clone, Debug, Parser)] @@ -29,10 +31,6 @@ pub struct InspectArgs { #[arg(value_enum)] pub field: ContractArtifactField, - /// Pretty print the selected field, if supported. - #[arg(long)] - pub pretty: bool, - /// All build arguments are supported #[command(flatten)] build: BuildOpts, @@ -40,7 +38,7 @@ pub struct InspectArgs { impl InspectArgs { pub fn run(self) -> Result<()> { - let Self { contract, field, build, pretty } = self; + let Self { contract, field, build } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); @@ -85,12 +83,7 @@ impl InspectArgs { .abi .as_ref() .ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; - if pretty { - let source = foundry_cli::utils::abi_to_solidity(abi, &contract.name)?; - sh_println!("{source}")?; - } else { - print_json(abi)?; - } + print_abi(abi)?; } ContractArtifactField::Bytecode => { print_json_str(&artifact.bytecode, Some("object"))?; @@ -105,7 +98,7 @@ impl InspectArgs { print_json_str(&artifact.legacy_assembly, None)?; } ContractArtifactField::MethodIdentifiers => { - print_json(&artifact.method_identifiers)?; + print_method_identifiers(&artifact.method_identifiers)?; } ContractArtifactField::GasEstimates => { print_json(&artifact.gas_estimates)?; @@ -117,10 +110,10 @@ impl InspectArgs { print_json(&artifact.devdoc)?; } ContractArtifactField::Ir => { - print_yul(artifact.ir.as_deref(), self.pretty)?; + print_yul(artifact.ir.as_deref())?; } ContractArtifactField::IrOptimized => { - print_yul(artifact.ir_optimized.as_deref(), self.pretty)?; + print_yul(artifact.ir_optimized.as_deref())?; } ContractArtifactField::Metadata => { print_json(&artifact.metadata)?; @@ -132,37 +125,12 @@ impl InspectArgs { print_json_str(&artifact.ewasm, None)?; } ContractArtifactField::Errors => { - let mut out = serde_json::Map::new(); - if let Some(abi) = &artifact.abi { - let abi = &abi; - // Print the signature of all errors. - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { - let types = er.inputs.iter().map(|p| p.ty.clone()).collect::>(); - let sig = format!("{:x}", er.selector()); - let sig_trimmed = &sig[0..8]; - out.insert( - format!("{}({})", er.name, types.join(",")), - sig_trimmed.to_string().into(), - ); - } - } - print_json(&out)?; + let out = artifact.abi.as_ref().map_or(Map::new(), parse_errors); + print_errors_events(&out, true)?; } ContractArtifactField::Events => { - let mut out = serde_json::Map::new(); - if let Some(abi) = &artifact.abi { - let abi = &abi; - // Print the topic of all events including anonymous. - for ev in abi.events.iter().flat_map(|(_, events)| events) { - let types = ev.inputs.iter().map(|p| p.ty.clone()).collect::>(); - let topic = hex::encode(keccak256(ev.signature())); - out.insert( - format!("{}({})", ev.name, types.join(",")), - format!("0x{topic}").into(), - ); - } - } - print_json(&out)?; + let out = artifact.abi.as_ref().map_or(Map::new(), parse_events); + print_errors_events(&out, false)?; } ContractArtifactField::Eof => { print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?; @@ -176,6 +144,127 @@ 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) { + let types = get_ty_sig(&er.inputs); + let sig = format!("{:x}", er.selector()); + let sig_trimmed = &sig[0..8]; + out.insert(format!("{}({})", er.name, types), sig_trimmed.to_string().into()); + } + out +} + +fn parse_events(abi: &JsonAbi) -> Map { + let mut out = serde_json::Map::new(); + for ev in abi.events.iter().flat_map(|(_, events)| events) { + let types = parse_event_params(&ev.inputs); + let topic = hex::encode(keccak256(ev.signature())); + out.insert(format!("{}({})", ev.name, types), format!("0x{topic}").into()); + } + out +} + +fn parse_event_params(ev_params: &[EventParam]) -> String { + ev_params + .iter() + .map(|p| { + if let Some(ty) = p.internal_type() { + return internal_ty(ty) + } + p.ty.clone() + }) + .collect::>() + .join(",") +} + +fn print_abi(abi: &JsonAbi) -> Result<()> { + if shell::is_json() { + 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 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 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]); + } + + 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(receive) = &abi.receive { + let state_mut = receive.state_mutability.as_json_str(); + table.add_row(["receive", &format!("receive() {state_mut}"), ""]); + } + }) +} + +fn get_ty_sig(inputs: &[Param]) -> String { + inputs + .iter() + .map(|p| { + if let Some(ty) = p.internal_type() { + return internal_ty(ty); + } + p.ty.clone() + }) + .collect::>() + .join(",") +} + +fn internal_ty(ty: &InternalType) -> String { + let contract_ty = + |c: &Option, ty: &String| c.clone().map_or(ty.clone(), |c| format!("{c}.{ty}")); + match ty { + InternalType::AddressPayable(addr) => addr.clone(), + InternalType::Contract(contract) => contract.clone(), + InternalType::Enum { contract, ty } => contract_ty(contract, ty), + InternalType::Struct { contract, ty } => contract_ty(contract, ty), + InternalType::Other { contract, ty } => contract_ty(contract, ty), + } +} + pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> { let Some(storage_layout) = storage_layout else { eyre::bail!("Could not get storage layout"); @@ -185,30 +274,70 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<() return print_json(&storage_layout) } - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); - - table.set_header(vec![ + let headers = vec![ Cell::new("Name"), Cell::new("Type"), Cell::new("Slot"), Cell::new("Offset"), Cell::new("Bytes"), Cell::new("Contract"), - ]); - - 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, + ]); + } + }) +} + +fn print_method_identifiers(method_identifiers: &Option>) -> Result<()> { + let Some(method_identifiers) = method_identifiers else { + eyre::bail!("Could not get method identifiers"); + }; + + if shell::is_json() { + 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]); + } + }) +} + +fn print_errors_events(map: &Map, is_err: bool) -> Result<()> { + if shell::is_json() { + return print_json(map); } + let headers = if is_err { + vec![Cell::new("Error"), Cell::new("Selector")] + } 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()]); + } + }) +} + +fn print_table(headers: Vec, add_rows: impl FnOnce(&mut Table)) -> Result<()> { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header(headers); + add_rows(&mut table); sh_println!("\n{table}\n")?; Ok(()) } @@ -407,7 +536,7 @@ fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> Ok(()) } -fn print_yul(yul: Option<&str>, pretty: bool) -> Result<()> { +fn print_yul(yul: Option<&str>) -> Result<()> { let Some(yul) = yul else { eyre::bail!("Could not get IR output"); }; @@ -415,11 +544,7 @@ fn print_yul(yul: Option<&str>, pretty: bool) -> Result<()> { static YUL_COMMENTS: LazyLock = LazyLock::new(|| Regex::new(r"(///.*\n\s*)|(\s*/\*\*.*\*/)").unwrap()); - if pretty { - sh_println!("{}", YUL_COMMENTS.replace_all(yul, ""))?; - } else { - sh_println!("{yul}")?; - } + sh_println!("{}", YUL_COMMENTS.replace_all(yul, ""))?; Ok(()) } diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index de9178a70f..f1b443d5a4 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -24,6 +24,7 @@ static DEPENDENCY_VERSION_TAG_REGEX: LazyLock = #[command(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... forge install [OPTIONS] /@... forge install [OPTIONS] =/@... + forge install [OPTIONS] @git url>...)] forge install [OPTIONS] ...")] pub struct InstallArgs { /// The dependencies to install. @@ -58,7 +59,7 @@ impl_figment_convert_basic!(InstallArgs); impl InstallArgs { pub fn run(self) -> Result<()> { - let mut config = self.try_load_config_emit_warnings()?; + let mut config = self.load_config()?; self.opts.install(&mut config, self.dependencies) } } diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index 6885819f81..d633564f69 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -1,43 +1,9 @@ -//! Subcommands for forge +//! `forge` subcommands. //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should //! implement `figment::Provider` which allows the subcommand to override the config's defaults, see //! [`foundry_config::Config`]. -//! -//! See [`BuildArgs`] for a reference implementation. -//! And [`DebugArgs`] for how to merge `Providers`. -//! -//! # Example -//! -//! create a `clap` subcommand into a `figment::Provider` and integrate it in the -//! `foundry_config::Config`: -//! -//! ``` -//! use clap::Parser; -//! use forge::executor::opts::EvmOpts; -//! use foundry_cli::cmd::forge::build::BuildArgs; -//! use foundry_common::evm::EvmArgs; -//! use foundry_config::{figment::Figment, *}; -//! -//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` -//! #[derive(Clone, Debug, Parser)] -//! pub struct MyArgs { -//! #[command(flatten)] -//! evm: EvmArgs, -//! #[command(flatten)] -//! build: BuildArgs, -//! } -//! -//! // add `Figment` and `Config` converters -//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); -//! let args = MyArgs::parse_from(["build"]); -//! -//! let figment: Figment = From::from(&args); -//! let evm_opts = figment.extract::().unwrap(); -//! -//! let config: Config = From::from(&args); -//! ``` pub mod bind; pub mod bind_json; diff --git a/crates/forge/bin/cmd/remappings.rs b/crates/forge/bin/cmd/remappings.rs index dfa667ccc9..c475e81995 100644 --- a/crates/forge/bin/cmd/remappings.rs +++ b/crates/forge/bin/cmd/remappings.rs @@ -21,7 +21,7 @@ impl_figment_convert_basic!(RemappingArgs); impl RemappingArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; if self.pretty { let mut groups = BTreeMap::<_, Vec<_>>::new(); diff --git a/crates/forge/bin/cmd/remove.rs b/crates/forge/bin/cmd/remove.rs index da2f8b2511..2033ad3a7c 100644 --- a/crates/forge/bin/cmd/remove.rs +++ b/crates/forge/bin/cmd/remove.rs @@ -29,7 +29,7 @@ impl_figment_convert_basic!(RemoveArgs); impl RemoveArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let (root, paths) = super::update::dependencies_paths(&self.dependencies, &config)?; let git_modules = root.join(".git/modules"); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 128474aed1..938df325c1 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -57,7 +57,7 @@ mod summary; pub use filter::FilterArgs; use forge::{result::TestKind, traces::render_trace_arena_inner}; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; -use summary::{print_invariant_metrics, TestSummaryReport}; +use summary::{format_invariant_metrics_table, TestSummaryReport}; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, build, evm); @@ -80,8 +80,8 @@ pub struct TestArgs { /// /// If the matching test is a fuzz test, then it will open the debugger on the first failure /// case. If the fuzz test does not fail, it will open the debugger on the last fuzz case. - #[arg(long, conflicts_with_all = ["flamegraph", "flamechart", "decode_internal", "rerun"], value_name = "DEPRECATED_TEST_FUNCTION_REGEX")] - debug: Option>, + #[arg(long, conflicts_with_all = ["flamegraph", "flamechart", "decode_internal", "rerun"])] + debug: bool, /// Generate a flamegraph for a single test. Implies `--decode-internal`. /// @@ -103,8 +103,8 @@ pub struct TestArgs { /// /// 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, value_name = "DEPRECATED_TEST_FUNCTION_REGEX")] - decode_internal: Option>, + #[arg(long)] + decode_internal: bool, /// Dumps all debugger steps to file. #[arg( @@ -269,7 +269,7 @@ impl TestArgs { /// Returns the test results for all matching tests. pub async fn execute_tests(mut self) -> Result { // Merge all configs. - let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + let (mut config, mut evm_opts) = self.load_config_and_evm_opts()?; let mut strategy = utils::get_executor_strategy(&config); // Explicitly enable isolation for gas reports for more correct gas accounting. @@ -284,13 +284,13 @@ impl TestArgs { // Install missing dependencies. if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings - config = self.load_config(); + config = self.load_config()?; } // Set up the project. let project = config.project()?; - let mut filter = self.filter(&config); + let filter = self.filter(&config); trace!(target: "forge::test", ?filter, "using filter"); let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; @@ -319,18 +319,7 @@ impl TestArgs { // Create test options from general project settings and compiler output. let project_root = &project.paths.root; - // Remove the snapshots directory if it exists. - // This is to ensure that we don't have any stale snapshots. - // If `FORGE_SNAPSHOT_CHECK` is set, we don't remove the snapshots directory as it is - // required for comparison. - if std::env::var_os("FORGE_SNAPSHOT_CHECK").is_none() { - let snapshot_dir = project_root.join(&config.snapshots); - if snapshot_dir.exists() { - let _ = fs::remove_dir_all(project_root.join(&config.snapshots)); - } - } - - let should_debug = self.debug.is_some(); + let should_debug = self.debug; let should_draw = self.flamegraph || self.flamechart; // Determine print verbosity and executor verbosity. @@ -342,12 +331,12 @@ impl TestArgs { let env = evm_opts.evm_env().await?; // Enable internal tracing for more informative flamegraph. - if should_draw && self.decode_internal.is_none() { - self.decode_internal = Some(None); + if should_draw && !self.decode_internal { + self.decode_internal = true; } // Choose the internal function tracing mode, if --decode-internal is provided. - let decode_internal = if self.decode_internal.is_some() { + let decode_internal = if self.decode_internal { // If more than one function matched, we enable simple tracing. // If only one function matched, we enable full tracing. This is done in `run_tests`. InternalTraceMode::Simple @@ -373,28 +362,6 @@ impl TestArgs { .enable_isolation(evm_opts.isolate) .build::(project_root, &output, zk_output, env, evm_opts, strategy)?; - let mut maybe_override_mt = |flag, maybe_regex: Option<&Option>| { - if let Some(Some(regex)) = maybe_regex { - sh_warn!( - "specifying argument for --{flag} is deprecated and will be removed in the future, \ - use --match-test instead" - )?; - - let test_pattern = &mut filter.args_mut().test_pattern; - if test_pattern.is_some() { - eyre::bail!( - "Cannot specify both --{flag} and --match-test. \ - Use --match-contract and --match-path to further limit the search instead." - ); - } - *test_pattern = Some(regex.clone()); - } - - Ok(()) - }; - maybe_override_mt("debug", self.debug.as_ref())?; - maybe_override_mt("decode-internal", self.decode_internal.as_ref())?; - let libraries = runner.libraries.clone(); let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; @@ -414,7 +381,7 @@ impl TestArgs { let mut fst = folded_stack_trace::build(arena); let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; - let contract = suite_name.split(':').last().unwrap(); + let contract = suite_name.split(':').next_back().unwrap(); let test_name = test_name.trim_end_matches("()"); let file_name = format!("cache/{label}_{contract}_{test_name}.svg"); let file = std::fs::File::create(&file_name).wrap_err("failed to create file")?; @@ -488,7 +455,7 @@ 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 != 1 && (self.debug.is_some() || self.flamegraph || self.flamechart) { + if num_filtered != 1 && (self.debug || self.flamegraph || self.flamechart) { let action = if self.flamegraph { "generate a flamegraph" } else if self.flamechart { @@ -508,7 +475,7 @@ impl TestArgs { } // If exactly one test matched, we enable full tracing. - if num_filtered == 1 && self.decode_internal.is_some() { + if num_filtered == 1 && self.decode_internal { runner.decode_internal = InternalTraceMode::Full; } @@ -571,7 +538,7 @@ impl TestArgs { )?); } - if self.decode_internal.is_some() { + if self.decode_internal { let sources = ContractSources::from_project_output(output, &config.root, Some(&libraries))?; builder = builder.with_debug_identifier(DebugTraceIdentifier::new(sources)); @@ -600,7 +567,7 @@ impl TestArgs { // We identify addresses if we're going to print *any* trace or gas report. let identify_addresses = verbosity >= 3 || self.gas_report || - self.debug.is_some() || + self.debug || self.flamegraph || self.flamechart; @@ -624,7 +591,9 @@ impl TestArgs { // Display invariant metrics if invariant kind. if let TestKind::Invariant { metrics, .. } = &result.kind { - print_invariant_metrics(metrics); + if !metrics.is_empty() { + let _ = sh_println!("\n{}\n", format_invariant_metrics_table(metrics)); + } } // We only display logs at level 2 and above @@ -858,8 +827,8 @@ impl TestArgs { /// bootstrap a new [`watchexe::Watchexec`] loop. pub(crate) fn watchexec_config(&self) -> Result { self.watch.watchexec_config(|| { - let config = Config::from(self); - [config.src, config.test] + let config = self.load_config()?; + Ok([config.src, config.test]) }) } } diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs index eabf7bd9ea..68ab3f4590 100644 --- a/crates/forge/bin/cmd/test/summary.rs +++ b/crates/forge/bin/cmd/test/summary.rs @@ -124,7 +124,7 @@ impl TestSummaryReport { } } -/// Helper function to print the invariant metrics. +/// Helper function to create the invariant metrics table. /// /// ╭-----------------------+----------------+-------+---------+----------╮ /// | Contract | Selector | Calls | Reverts | Discards | @@ -137,55 +137,90 @@ impl TestSummaryReport { /// |-----------------------+----------------+-------+---------+----------| /// | CounterHandler | doSomething | 7382 | 160 |4794 | /// ╰-----------------------+----------------+-------+---------+----------╯ -pub(crate) fn print_invariant_metrics(test_metrics: &HashMap) { - if !test_metrics.is_empty() { - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); +pub(crate) fn format_invariant_metrics_table( + test_metrics: &HashMap, +) -> Table { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Contract"), + Cell::new("Selector"), + Cell::new("Calls").fg(Color::Green), + Cell::new("Reverts").fg(Color::Red), + Cell::new("Discards").fg(Color::Yellow), + ]); + + for name in test_metrics.keys().sorted() { + if let Some((contract, selector)) = + name.split_once(':').map_or(name.as_str(), |(_, contract)| contract).split_once('.') + { + let mut row = Row::new(); + row.add_cell(Cell::new(contract)); + row.add_cell(Cell::new(selector)); + + if let Some(metrics) = test_metrics.get(name) { + let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { + Color::Green + } else { + Color::White + }); - table.set_header(vec![ - Cell::new("Contract"), - Cell::new("Selector"), - Cell::new("Calls").fg(Color::Green), - Cell::new("Reverts").fg(Color::Red), - Cell::new("Discards").fg(Color::Yellow), - ]); + let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { + Color::Red + } else { + Color::White + }); - for name in test_metrics.keys().sorted() { - if let Some((contract, selector)) = - name.split_once(':').and_then(|(_, contract)| contract.split_once('.')) - { - let mut row = Row::new(); - row.add_cell(Cell::new(contract)); - row.add_cell(Cell::new(selector)); - - if let Some(metrics) = test_metrics.get(name) { - let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { - Color::Green - } else { - Color::White - }); - - let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { - Color::Red - } else { - Color::White - }); - - let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { - Color::Yellow - } else { - Color::White - }); - - row.add_cell(calls_cell); - row.add_cell(reverts_cell); - row.add_cell(discards_cell); - } + let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { + Color::Yellow + } else { + Color::White + }); - table.add_row(row); + row.add_cell(calls_cell); + row.add_cell(reverts_cell); + row.add_cell(discards_cell); } + + table.add_row(row); } + } + table +} - let _ = sh_println!("\n{table}\n"); +#[cfg(test)] +mod tests { + use crate::cmd::test::summary::format_invariant_metrics_table; + use foundry_evm::executors::invariant::InvariantMetrics; + use std::collections::HashMap; + + #[test] + fn test_invariant_metrics_table() { + let mut test_metrics = HashMap::new(); + test_metrics.insert( + "SystemConfig.setGasLimit".to_string(), + InvariantMetrics { calls: 10, reverts: 1, discards: 1 }, + ); + test_metrics.insert( + "src/universal/Proxy.sol:Proxy.changeAdmin".to_string(), + InvariantMetrics { calls: 20, reverts: 2, discards: 2 }, + ); + let table = format_invariant_metrics_table(&test_metrics); + assert_eq!(table.row_count(), 2); + + let mut first_row_content = table.row(0).unwrap().cell_iter(); + assert_eq!(first_row_content.next().unwrap().content(), "SystemConfig"); + assert_eq!(first_row_content.next().unwrap().content(), "setGasLimit"); + assert_eq!(first_row_content.next().unwrap().content(), "10"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + + let mut second_row_content = table.row(1).unwrap().cell_iter(); + assert_eq!(second_row_content.next().unwrap().content(), "Proxy"); + assert_eq!(second_row_content.next().unwrap().content(), "changeAdmin"); + assert_eq!(second_row_content.next().unwrap().content(), "20"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); } } diff --git a/crates/forge/bin/cmd/tree.rs b/crates/forge/bin/cmd/tree.rs index fe278e98cb..b97d7c8d98 100644 --- a/crates/forge/bin/cmd/tree.rs +++ b/crates/forge/bin/cmd/tree.rs @@ -27,7 +27,7 @@ foundry_config::impl_figment_convert!(TreeArgs, project_paths); impl TreeArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let graph = 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/bin/cmd/update.rs b/crates/forge/bin/cmd/update.rs index c61b03d7a0..5e965c34a9 100644 --- a/crates/forge/bin/cmd/update.rs +++ b/crates/forge/bin/cmd/update.rs @@ -32,7 +32,7 @@ impl_figment_convert_basic!(UpdateArgs); impl UpdateArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let (root, paths) = dependencies_paths(&self.dependencies, &config)?; // fetch the latest changes for each submodule (recursively if flag is set) let git = Git::new(&root); diff --git a/crates/forge/bin/cmd/watch.rs b/crates/forge/bin/cmd/watch.rs index 0550d4562a..02303753fb 100644 --- a/crates/forge/bin/cmd/watch.rs +++ b/crates/forge/bin/cmd/watch.rs @@ -1,8 +1,11 @@ -use super::{build::BuildArgs, doc::DocArgs, snapshot::GasSnapshotArgs, test::TestArgs}; +use super::{ + build::BuildArgs, coverage::CoverageArgs, doc::DocArgs, snapshot::GasSnapshotArgs, + test::TestArgs, +}; use alloy_primitives::map::HashSet; use clap::Parser; use eyre::Result; -use foundry_cli::utils::{self, FoundryPathExt}; +use foundry_cli::utils::{self, FoundryPathExt, LoadConfig}; use foundry_config::Config; use parking_lot::Mutex; use std::{ @@ -70,7 +73,7 @@ impl WatchArgs { /// otherwise the path the closure returns will be used. pub fn watchexec_config, P: Into>( &self, - default_paths: impl FnOnce() -> PS, + default_paths: impl FnOnce() -> Result, ) -> Result { self.watchexec_config_generic(default_paths, None) } @@ -81,7 +84,7 @@ impl WatchArgs { /// otherwise the path the closure returns will be used. pub fn watchexec_config_with_override, P: Into>( &self, - default_paths: impl FnOnce() -> PS, + default_paths: impl FnOnce() -> Result, spawn_hook: impl Fn(&[Event], &mut TokioCommand) + Send + Sync + 'static, ) -> Result { self.watchexec_config_generic(default_paths, Some(Arc::new(spawn_hook))) @@ -89,13 +92,13 @@ impl WatchArgs { fn watchexec_config_generic, P: Into>( &self, - default_paths: impl FnOnce() -> PS, + default_paths: impl FnOnce() -> Result, spawn_hook: Option, ) -> Result { let mut paths = self.watch.as_deref().unwrap_or_default(); let storage: Vec<_>; if paths.is_empty() { - storage = default_paths().into_iter().map(Into::into).filter(|p| p.exists()).collect(); + storage = default_paths()?.into_iter().map(Into::into).filter(|p| p.exists()).collect(); paths = &storage; } self.watchexec_config_inner(paths, spawn_hook) @@ -259,7 +262,7 @@ pub async fn watch_gas_snapshot(args: GasSnapshotArgs) -> Result<()> { /// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge /// test` pub async fn watch_test(args: TestArgs) -> Result<()> { - let config: Config = Config::from(&args.build); + 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() || @@ -270,7 +273,7 @@ pub async fn watch_test(args: TestArgs) -> Result<()> { let last_test_files = Mutex::new(HashSet::::default()); let project_root = config.root.to_string_lossy().into_owned(); let config = args.watch.watchexec_config_with_override( - || [&config.test, &config.src], + || Ok([&config.test, &config.src]), move |events, command| { let mut changed_sol_test_files: HashSet<_> = events .iter() @@ -311,18 +314,24 @@ pub async fn watch_test(args: TestArgs) -> Result<()> { } }, )?; - run(config).await?; + run(config).await +} - Ok(()) +pub async fn watch_coverage(args: CoverageArgs) -> Result<()> { + let config = args.watch().watchexec_config(|| { + let config = args.load_config()?; + Ok([config.test, config.src]) + })?; + run(config).await } /// Executes a [`Watchexec`] that listens for changes in the project's sources directory pub async fn watch_doc(args: DocArgs) -> Result<()> { - let src_path = args.config()?.src; - let config = args.watch.watchexec_config(|| [src_path])?; - run(config).await?; - - Ok(()) + let config = args.watch.watchexec_config(|| { + let config = args.config()?; + Ok([config.src]) + })?; + run(config).await } /// Converts a list of arguments to a `watchexec::Command`. diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index e8a06c2ba5..3db3848191 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -49,7 +49,13 @@ fn run() -> Result<()> { } } ForgeSubcommand::Script(cmd) => utils::block_on(cmd.run_script()), - ForgeSubcommand::Coverage(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Coverage(cmd) => { + if cmd.is_watch() { + utils::block_on(watch::watch_coverage(cmd)) + } else { + utils::block_on(cmd.run()) + } + } ForgeSubcommand::Bind(cmd) => cmd.run(), ForgeSubcommand::Build(cmd) => { if cmd.is_watch() { @@ -86,7 +92,7 @@ fn run() -> Result<()> { Ok(()) } ForgeSubcommand::Clean { root } => { - let config = utils::load_config_with_root(root.as_deref()); + let config = utils::load_config_with_root(root.as_deref())?; let project = config.project()?; let zk_project = foundry_config::zksync::config_create_project(&config, config.cache, false)?; diff --git a/crates/forge/bin/opts.rs b/crates/forge/bin/opts.rs index e211d03b78..86a23d7408 100644 --- a/crates/forge/bin/opts.rs +++ b/crates/forge/bin/opts.rs @@ -9,22 +9,15 @@ use clap::{Parser, Subcommand, ValueHint}; use forge_script::ScriptArgs; use forge_verify::{VerifyArgs, VerifyBytecodeArgs, VerifyCheckArgs}; use foundry_cli::opts::GlobalArgs; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::path::PathBuf; -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); - /// Build, test, fuzz, debug and deploy Solidity contracts. #[derive(Parser)] #[command( name = "forge", - version = VERSION_MESSAGE, + version = SHORT_VERSION, + long_version = LONG_VERSION, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html", next_display_order = None, )] diff --git a/crates/forge/build.rs b/crates/forge/build.rs deleted file mode 100644 index c2f550fb6f..0000000000 --- a/crates/forge/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index ff3cac46eb..9fc29bd2b8 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -15,7 +15,13 @@ pub use foundry_evm::coverage::*; /// A coverage reporter. pub trait CoverageReporter { - fn report(self, report: &CoverageReport) -> eyre::Result<()>; + /// Returns `true` if the reporter needs source maps for the final report. + fn needs_source_maps(&self) -> bool { + false + } + + /// Runs the reporter. + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()>; } /// A simple summary reporter that prints the coverage results in a table. @@ -56,7 +62,7 @@ impl CoverageSummaryReporter { } impl CoverageReporter for CoverageSummaryReporter { - fn report(mut self, report: &CoverageReport) -> eyre::Result<()> { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { self.total.merge(&summary); self.add_row(path.display(), summary); @@ -89,26 +95,28 @@ fn format_cell(hits: usize, total: usize) -> Cell { /// /// [LCOV]: https://github.com/linux-test-project/lcov /// [tracefile format]: https://man.archlinux.org/man/geninfo.1.en#TRACEFILE_FORMAT -pub struct LcovReporter<'a> { - out: &'a mut (dyn Write + 'a), +pub struct LcovReporter { + path: PathBuf, version: Version, } -impl<'a> LcovReporter<'a> { +impl LcovReporter { /// Create a new LCOV reporter. - pub fn new(out: &'a mut (dyn Write + 'a), version: Version) -> Self { - Self { out, version } + pub fn new(path: PathBuf, version: Version) -> Self { + Self { path, version } } } -impl CoverageReporter for LcovReporter<'_> { - fn report(self, report: &CoverageReport) -> eyre::Result<()> { +impl CoverageReporter for LcovReporter { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { + let mut out = std::io::BufWriter::new(fs::create_file(&self.path)?); + let mut fn_index = 0usize; for (path, items) in report.items_by_file() { let summary = CoverageSummary::from_items(items.iter().copied()); - writeln!(self.out, "TN:")?; - writeln!(self.out, "SF:{}", path.display())?; + writeln!(out, "TN:")?; + writeln!(out, "SF:{}", path.display())?; for item in items { let line = item.loc.lines.start; @@ -120,24 +128,24 @@ impl CoverageReporter for LcovReporter<'_> { let name = format!("{}.{name}", item.loc.contract_name); if self.version >= Version::new(2, 2, 0) { // v2.2 changed the FN format. - writeln!(self.out, "FNL:{fn_index},{line},{end_line}")?; - writeln!(self.out, "FNA:{fn_index},{hits},{name}")?; + writeln!(out, "FNL:{fn_index},{line},{end_line}")?; + writeln!(out, "FNA:{fn_index},{hits},{name}")?; fn_index += 1; } else if self.version >= Version::new(2, 0, 0) { // v2.0 added end_line to FN. - writeln!(self.out, "FN:{line},{end_line},{name}")?; - writeln!(self.out, "FNDA:{hits},{name}")?; + writeln!(out, "FN:{line},{end_line},{name}")?; + writeln!(out, "FNDA:{hits},{name}")?; } else { - writeln!(self.out, "FN:{line},{name}")?; - writeln!(self.out, "FNDA:{hits},{name}")?; + writeln!(out, "FN:{line},{name}")?; + writeln!(out, "FNDA:{hits},{name}")?; } } CoverageItemKind::Line => { - writeln!(self.out, "DA:{line},{hits}")?; + writeln!(out, "DA:{line},{hits}")?; } CoverageItemKind::Branch { branch_id, path_id, .. } => { writeln!( - self.out, + out, "BRDA:{line},{branch_id},{path_id},{}", if hits == 0 { "-".to_string() } else { hits.to_string() } )?; @@ -149,20 +157,21 @@ impl CoverageReporter for LcovReporter<'_> { } // Function summary - writeln!(self.out, "FNF:{}", summary.function_count)?; - writeln!(self.out, "FNH:{}", summary.function_hits)?; + writeln!(out, "FNF:{}", summary.function_count)?; + writeln!(out, "FNH:{}", summary.function_hits)?; // Line summary - writeln!(self.out, "LF:{}", summary.line_count)?; - writeln!(self.out, "LH:{}", summary.line_hits)?; + writeln!(out, "LF:{}", summary.line_count)?; + writeln!(out, "LH:{}", summary.line_hits)?; // Branch summary - writeln!(self.out, "BRF:{}", summary.branch_count)?; - writeln!(self.out, "BRH:{}", summary.branch_hits)?; + writeln!(out, "BRF:{}", summary.branch_count)?; + writeln!(out, "BRH:{}", summary.branch_hits)?; - writeln!(self.out, "end_of_record")?; + writeln!(out, "end_of_record")?; } + out.flush()?; sh_println!("Wrote LCOV report.")?; Ok(()) @@ -173,40 +182,40 @@ impl CoverageReporter for LcovReporter<'_> { pub struct DebugReporter; impl CoverageReporter for DebugReporter { - fn report(self, report: &CoverageReport) -> eyre::Result<()> { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, items) in report.items_by_file() { sh_println!("Uncovered for {}:", path.display())?; - items.iter().for_each(|item| { + for item in items { if item.hits == 0 { - let _ = sh_println!("- {item}"); + sh_println!("- {item}")?; } - }); + } sh_println!()?; } for (contract_id, anchors) in &report.anchors { sh_println!("Anchors for {contract_id}:")?; - anchors + let anchors = anchors .0 .iter() .map(|anchor| (false, anchor)) - .chain(anchors.1.iter().map(|anchor| (true, anchor))) - .for_each(|(is_deployed, anchor)| { - let _ = sh_println!("- {anchor}"); - if is_deployed { - let _ = sh_println!("- Creation code"); - } else { - let _ = sh_println!("- Runtime code"); - } - let _ = sh_println!( - " - Refers to item: {}", - report - .items - .get(&contract_id.version) - .and_then(|items| items.get(anchor.item_id)) - .map_or("None".to_owned(), |item| item.to_string()) - ); - }); + .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")?; + } + sh_println!( + " - Refers to item: {}", + report + .analyses + .get(&contract_id.version) + .and_then(|items| items.get(anchor.item_id)) + .map_or_else(|| "None".to_owned(), |item| item.to_string()) + )?; + } sh_println!()?; } @@ -226,9 +235,15 @@ impl BytecodeReporter { } impl CoverageReporter for BytecodeReporter { - fn report(self, report: &CoverageReport) -> eyre::Result<()> { + fn needs_source_maps(&self) -> bool { + true + } + + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { use std::fmt::Write; + fs::create_dir_all(&self.destdir)?; + let no_source_elements = Vec::new(); let mut line_number_cache = LineNumberCache::new(self.root.clone()); @@ -241,7 +256,7 @@ impl CoverageReporter for BytecodeReporter { for (code, source_element) in std::iter::zip(ops.iter(), source_elements) { let hits = hits - .get(code.offset as usize) + .get(code.offset) .map(|h| format!("[{h:03}]")) .unwrap_or(" ".to_owned()); let source_id = source_element.index(); diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 044b287576..330465217e 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -11,8 +11,7 @@ use foundry_common::{ reports::{report_kind, ReportKind}, TestFunctionExt, }; -use foundry_evm::traces::CallKind; -use foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS; +use foundry_evm::{constants::HARDHAT_CONSOLE_ADDRESS, traces::CallKind}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::{collections::BTreeMap, fmt::Display}; diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index ddeada0a69..27dd63738f 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,4 +1,5 @@ -#![doc = include_str!("../README.md")] +//! Forge is a fast and flexible Ethereum testing framework. + #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 09c931c1aa..71bb85f5d0 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -358,8 +358,7 @@ impl TestRunnerConfig { &self.config, self.evm_opts.clone(), Some(known_contracts), - Some(artifact_id.name.clone()), - Some(artifact_id.version.clone()), + Some(artifact_id.clone()), strategy.runner.new_cheatcode_inspector_strategy(strategy.context.as_ref()), )); diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 76dd718c60..7c9355471d 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -72,6 +72,7 @@ contract Dummy { }); forgetest!(initcode_size_exceeds_limit, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_source("LargeContract", generate_large_contract(5450).as_str()).unwrap(); cmd.args(["build", "--sizes"]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -103,6 +104,7 @@ Compiler run successful! }); forgetest!(initcode_size_limit_can_be_ignored, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_source("LargeContract", generate_large_contract(5450).as_str()).unwrap(); cmd.args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -149,6 +151,7 @@ Compiler run successful! // tests build output is as expected forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { prj.write_config(Config { + optimizer: Some(true), solc: Some(foundry_config::SolcReq::Version(semver::Version::new(0, 8, 27))), ..Default::default() }); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index cc378f8c25..89a713b490 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1574,6 +1574,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { // report for all prj.write_config(Config { + optimizer: Some(true), gas_reports: (vec!["*".to_string()]), gas_reports_ignore: (vec![]), ..Default::default() @@ -1683,7 +1684,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) .is_json(), ); - prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); + prj.write_config(Config { optimizer: Some(true), gas_reports: (vec![]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1788,7 +1789,11 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) .is_json(), ); - prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); + prj.write_config(Config { + optimizer: Some(true), + gas_reports: (vec!["*".to_string()]), + ..Default::default() + }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... ╭----------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -1894,6 +1899,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) ); prj.write_config(Config { + optimizer: Some(true), gas_reports: (vec![ "ContractOne".to_string(), "ContractTwo".to_string(), @@ -2011,7 +2017,11 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); // report for One - prj.write_config(Config { gas_reports: vec!["ContractOne".to_string()], ..Default::default() }); + prj.write_config(Config { + optimizer: Some(true), + gas_reports: vec!["ContractOne".to_string()], + ..Default::default() + }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... @@ -2058,7 +2068,11 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) ); // report for Two - prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); + prj.write_config(Config { + optimizer: Some(true), + gas_reports: vec!["ContractTwo".to_string()], + ..Default::default() + }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... @@ -2106,6 +2120,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) // report for Three prj.write_config(Config { + optimizer: Some(true), gas_reports: vec!["ContractThree".to_string()], ..Default::default() }); @@ -2161,6 +2176,7 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { // ignore ContractOne prj.write_config(Config { + optimizer: Some(true), gas_reports: (vec!["*".to_string()]), gas_reports_ignore: (vec!["ContractOne".to_string()]), ..Default::default() @@ -2243,6 +2259,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) // ignore ContractTwo cmd.forge_fuse(); prj.write_config(Config { + optimizer: Some(true), gas_reports: (vec![]), gas_reports_ignore: (vec!["ContractTwo".to_string()]), ..Default::default() @@ -2329,6 +2346,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) // indicating the "double listing". cmd.forge_fuse(); prj.write_config(Config { + optimizer: Some(true), gas_reports: (vec![ "ContractOne".to_string(), "ContractTwo".to_string(), @@ -2462,6 +2480,7 @@ Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. }); forgetest!(gas_report_flatten_multiple_selectors, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.insert_ds_test(); prj.add_source( "Counter.sol", @@ -2580,6 +2599,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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_test( "DelegateProxyTest.sol", r#" @@ -2723,6 +2743,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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_test( "NestedDeployTest.sol", r#" @@ -3161,6 +3182,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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.clear_cache(); cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" @@ -3209,24 +3231,183 @@ Compiler run successful! .stdout_eq(str![[r#""{...}""#]].is_json()); }); -// forgetest_init!(can_inspect_counter_pretty, |prj, cmd| { - cmd.args(["inspect", "src/Counter.sol:Counter", "abi", "--pretty"]).assert_success().stdout_eq( - str![[r#" -interface Counter { - function increment() external; - function number() external view returns (uint256); - function setNumber(uint256 newNumber) external; + cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#" + +╭----------+---------------------------------+------------╮ +| Type | Signature | Selector | ++=========================================================+ +| function | increment() nonpayable | 0xd09de08a | +|----------+---------------------------------+------------| +| function | number() view returns (uint256) | 0x8381f58a | +|----------+---------------------------------+------------| +| function | setNumber(uint256) nonpayable | 0x3fb5c1cb | +╰----------+---------------------------------+------------╯ + + +"#]]); +}); + +const CUSTOM_COUNTER: &str = r#" + contract Counter { + uint256 public number; + uint64 public count; + struct MyStruct { + uint64 count; + } + struct ErrWithMsg { + string message; + } + + event Incremented(uint256 newValue); + event Decremented(uint256 newValue); + + error NumberIsZero(); + error CustomErr(ErrWithMsg e); + + constructor(uint256 _number) { + number = _number; + } + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() external { + number++; + } + + function decrement() public payable { + if (number == 0) { + return; + } + number--; + } + + function square() public { + number = number * number; + } + + fallback() external payable { + ErrWithMsg memory err = ErrWithMsg("Fallback function is not allowed"); + revert CustomErr(err); + } + + receive() external payable { + count++; + } + + function setStruct(MyStruct memory s, uint32 b) public { + count = s.count; + } } + "#; +forgetest!(inspect_custom_counter_abi, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "abi"]).assert_success().stdout_eq(str![[r#" + +╭-------------+-----------------------------------------------+--------------------------------------------------------------------╮ +| Type | Signature | Selector | ++==================================================================================================================================+ +| event | Decremented(uint256) | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| error | CustomErr(Counter.ErrWithMsg) | 0x0625625a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| error | NumberIsZero() | 0xde5d32ac | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | count() view returns (uint64) | 0x06661abd | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | decrement() payable | 0x2baeceb7 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | increment() nonpayable | 0xd09de08a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | number() view returns (uint256) | 0x8381f58a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | setNumber(uint256) nonpayable | 0x3fb5c1cb | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | setStruct(Counter.MyStruct,uint32) nonpayable | 0x08ef7366 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | square() nonpayable | 0xd742cb01 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| constructor | constructor(uint256) nonpayable | | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| fallback | fallback() payable | | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| receive | receive() payable | | +╰-------------+-----------------------------------------------+--------------------------------------------------------------------╯ -"#]], - ); +"#]]); +}); + +forgetest!(inspect_custom_counter_events, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "events"]).assert_success().stdout_eq(str![[r#" + +╭----------------------+--------------------------------------------------------------------╮ +| Event | Topic | ++===========================================================================================+ +| Decremented(uint256) | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a | +|----------------------+--------------------------------------------------------------------| +| Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +╰----------------------+--------------------------------------------------------------------╯ + + +"#]]); +}); + +forgetest!(inspect_custom_counter_errors, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "errors"]).assert_success().stdout_eq(str![[r#" + +╭-------------------------------+----------╮ +| Error | Selector | ++==========================================+ +| CustomErr(Counter.ErrWithMsg) | 0625625a | +|-------------------------------+----------| +| NumberIsZero() | de5d32ac | +╰-------------------------------+----------╯ + + +"#]]); +}); + +forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#" + +╭----------------------------+------------╮ +| Method | Identifier | ++=========================================+ +| count() | 06661abd | +|----------------------------+------------| +| decrement() | 2baeceb7 | +|----------------------------+------------| +| increment() | d09de08a | +|----------------------------+------------| +| number() | 8381f58a | +|----------------------------+------------| +| setNumber(uint256) | 3fb5c1cb | +|----------------------------+------------| +| setStruct((uint64),uint32) | 08ef7366 | +|----------------------------+------------| +| square() | d742cb01 | +╰----------------------------+------------╯ + + +"#]]); }); // checks that `clean` also works with the "out" value set in Config forgetest_init!(gas_report_include_tests, |prj, cmd| { prj.write_config(Config { + optimizer: Some(true), gas_reports_include_tests: true, fuzz: FuzzConfig { runs: 1, ..Default::default() }, ..Default::default() @@ -3352,3 +3533,34 @@ forgetest_init!(zk_can_init_with_zksync, |prj, cmd| { // Assert that forge-zksync-std is installed assert!(prj.root().join("lib/forge-zksync-std").exists()); }); + +// +forgetest_init!(can_bind_enum_modules, |prj, cmd| { + prj.clear(); + + prj.add_source( + "Enum.sol", + r#" + contract Enum { + enum MyEnum { A, B, C } + } + "#, + ) + .unwrap(); + + prj.add_source( + "UseEnum.sol", + r#" + import "./Enum.sol"; + contract UseEnum { + Enum.MyEnum public myEnum; + }"#, + ) + .unwrap(); + + cmd.arg("bind").assert_success().stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Generating bindings for 11 contracts +Bindings have been generated to [..]"#]]); +}); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index cff6852cb1..a50033c4b5 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -50,8 +50,8 @@ forgetest!(can_extract_config_values, |prj, cmd| { auto_detect_solc: false, auto_detect_remappings: true, offline: true, - optimizer: false, - optimizer_runs: 1000, + optimizer: Some(false), + optimizer_runs: Some(1000), optimizer_details: Some(OptimizerDetails { yul: Some(false), yul_details: Some(YulDetails { stack_allocation: Some(true), ..Default::default() }), @@ -87,6 +87,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { ..Default::default() }, ffi: true, + allow_internal_expect_revert: false, always_use_create_2_factory: false, prompt_timeout: 0, sender: "00a329c0648769A73afAc7F9381D08FB43dBEA72".parse().unwrap(), @@ -172,7 +173,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { // tests config gets printed to std out forgetest!(can_show_config, |prj, cmd| { let expected = - Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); + Config::load_with_root(prj.root()).unwrap().to_string_pretty().unwrap().trim().to_string(); let output = cmd.arg("config").assert_success().get_output().stdout_lossy().trim().to_string(); assert_eq!(expected, output); }); @@ -186,7 +187,7 @@ forgetest_init!(can_override_config, |prj, cmd| { let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); - let profile = Config::load_with_root(prj.root()); + let profile = Config::load_with_root(prj.root()).unwrap(); // ensure that the auto-generated internal remapping for forge-std's ds-test exists assert_eq!(profile.remappings.len(), 1); assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); @@ -206,7 +207,7 @@ forgetest_init!(can_override_config, |prj, cmd| { // remappings work let remappings_txt = prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); - let config = forge_utils::load_config_with_root(Some(prj.root())); + let config = forge_utils::load_config_with_root(Some(prj.root())).unwrap(); assert_eq!( format!( "ds-test/={}/", @@ -252,7 +253,7 @@ forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); - let profile = Config::load_with_root(prj.root()); + let profile = Config::load_with_root(prj.root()).unwrap(); // ensure that the auto-generated internal remapping for forge-std's ds-test exists assert_eq!(profile.remappings.len(), 1); let r = &profile.remappings[0]; @@ -276,13 +277,13 @@ Installing solmate in [..] (url: Some("https://github.com/transmissions11/solmat }; install(&mut cmd, "transmissions11/solmate"); - let profile = Config::load_with_root(prj.root()); + let profile = Config::load_with_root(prj.root()).unwrap(); // remappings work let remappings_txt = prj.create_file( "remappings.txt", "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", ); - let config = forge_utils::load_config_with_root(Some(prj.root())); + let config = forge_utils::load_config_with_root(Some(prj.root())).unwrap(); // trailing slashes are removed on windows `to_slash_lossy` let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); #[cfg(windows)] @@ -317,7 +318,7 @@ forgetest_init!(can_detect_config_vals, |prj, _cmd| { assert!(!config.auto_detect_solc); assert_eq!(config.eth_rpc_url, Some(url.to_string())); - let mut config = Config::load_with_root(prj.root()); + let mut config = Config::load_with_root(prj.root()).unwrap(); config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); config.auto_detect_solc = false; // write to `foundry.toml` @@ -427,6 +428,7 @@ Compiler run successful! // test to ensure yul optimizer can be set as intended forgetest!(can_set_yul_optimizer, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_source( "foo.sol", r" @@ -476,14 +478,35 @@ forgetest_init!(can_parse_dapp_libraries, |_prj, cmd| { // test that optimizer runs works forgetest!(can_set_optimizer_runs, |prj, cmd| { // explicitly set optimizer runs - let config = Config { optimizer_runs: 1337, ..Default::default() }; + let config = Config { optimizer_runs: Some(1337), ..Default::default() }; prj.write_config(config); let config = cmd.config(); - assert_eq!(config.optimizer_runs, 1337); + assert_eq!(config.optimizer_runs, Some(1337)); let config = prj.config_from_output(["--optimizer-runs", "300"]); - assert_eq!(config.optimizer_runs, 300); + assert_eq!(config.optimizer_runs, Some(300)); +}); + +// +forgetest!(enable_optimizer_when_runs_set, |prj, cmd| { + // explicitly set optimizer runs + let config = Config { optimizer_runs: Some(1337), ..Default::default() }; + assert!(config.optimizer.is_none()); + prj.write_config(config); + + let config = cmd.config(); + assert!(config.optimizer.unwrap()); +}); + +// test `optimizer_runs` set to 200 by default if optimizer enabled +forgetest!(optimizer_runs_default, |prj, cmd| { + // explicitly set optimizer runs + let config = Config { optimizer: Some(true), ..Default::default() }; + prj.write_config(config); + + let config = cmd.config(); + assert_eq!(config.optimizer_runs, Some(200)); }); // test that gas_price can be set @@ -624,12 +647,18 @@ forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { // E.g. `@utils/libraries` mapping from library shouldn't be added if project already has `@utils` // remapping. // See -forgetest_init!(test_root_remappings_priority, |prj, cmd| { +// Test that +// - 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| { let mut config = cmd.config(); // Add `@utils/` remapping in project config. config.remappings = vec![ Remapping::from_str("@utils/=src/").unwrap().into(), - Remapping::from_str("@another-utils/libraries/=src/").unwrap().into(), + Remapping::from_str("@openzeppelin/contracts=lib/openzeppelin-contracts/").unwrap().into(), ]; let proj_toml_file = prj.paths().root.join("foundry.toml"); pretty_err(&proj_toml_file, fs::write(&proj_toml_file, config.to_string_pretty().unwrap())); @@ -638,10 +667,17 @@ forgetest_init!(test_root_remappings_priority, |prj, cmd| { // This should be filtered out from final remappings as root project already has `@utils/`. let nested = prj.paths().libraries[0].join("dep1"); pretty_err(&nested, fs::create_dir_all(&nested)); - let mut lib_config = Config::load_with_root(&nested); + let mut lib_config = Config::load_with_root(&nested).unwrap(); lib_config.remappings = vec![ Remapping::from_str("@utils/libraries/=src/").unwrap().into(), - Remapping::from_str("@another-utils/=src/").unwrap().into(), + Remapping::from_str("@openzeppelin/contracts-upgradeable/=lib/openzeppelin-upgradeable/") + .unwrap() + .into(), + Remapping::from_str( + "@openzeppelin/contracts/upgradeable/=lib/openzeppelin-contracts/upgradeable/", + ) + .unwrap() + .into(), ]; let lib_toml_file = nested.join("foundry.toml"); pretty_err(&lib_toml_file, fs::write(&lib_toml_file, lib_config.to_string_pretty().unwrap())); @@ -649,8 +685,8 @@ forgetest_init!(test_root_remappings_priority, |prj, cmd| { cmd.args(["remappings", "--pretty"]).assert_success().stdout_eq(str![[r#" Global: - @utils/=src/ -- @another-utils/libraries/=src/ -- @another-utils/=lib/dep1/src/ +- @openzeppelin/contracts/=lib/openzeppelin-contracts/ +- @openzeppelin/contracts-upgradeable/=lib/dep1/lib/openzeppelin-upgradeable/ - dep1/=lib/dep1/src/ - forge-std/=lib/forge-std/src/ @@ -885,7 +921,7 @@ contract MyScript is BaseScript { let nested = prj.paths().libraries[0].join("another-dep"); pretty_err(&nested, fs::create_dir_all(&nested)); - let mut lib_config = Config::load_with_root(&nested); + let mut lib_config = Config::load_with_root(&nested).unwrap(); lib_config.remappings = vec![ Remapping::from_str("test/=test/").unwrap().into(), Remapping::from_str("script/=script/").unwrap().into(), @@ -927,3 +963,442 @@ contract CounterTest { .unwrap(); cmd.forge_fuse().args(["build"]).assert_success(); }); + +// NOTE(zk): This test output differs from the original due to zksync integration: +// 1. Additional zksync-specific configuration items are present +// 2. Some existing configuration items appear in a different order +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(test_default_config, |prj, cmd| { + 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 +cache_path = "cache" +snapshots = "snapshots" +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 +additional_compiler_profiles = [] +eof = 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 +compilation_restrictions = [] +legacy_assertions = false +isolate = false +disable_block_gas_limit = false +transaction_timeout = 120 +unchecked_cheatcode_artifacts = false +create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" +create2_deployer = "0x4e59b44847b379578588920ca78fbf26c0b4956c" +odyssey = false +assertions_revert = true + +[[profile.default.fs_permissions]] +access = "read" +path = "out" + +[profile.default.rpc_storage_caching] +chains = "all" +endpoints = "all" + +[profile.default.zksync] +compile = false +startup = false +fallback_oz = false +enable_eravm_extensions = false +force_evmla = false +llvm_options = [] +optimizer = true +optimizer_mode = "3" +suppressed_warnings = [] +suppressed_errors = [] + +[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 + +[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" +no_zksync_reserved_addresses = false +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 +no_zksync_reserved_addresses = false + +[labels] + +[vyper] + +[bind_json] +out = "utils/JsonBindings.sol" +include = [] +exclude = [] + + +"#]]); + + cmd.forge_fuse().args(["config", "--json"]).assert_success().stdout_eq(str![[r#" +{ + "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", + "snapshots": "snapshots", + "broadcast": "broadcast", + "allow_paths": [], + "include_paths": [], + "skip": [], + "force": false, + "evm_version": "cancun", + "gas_reports": [ + "*" + ], + "gas_reports_ignore": [], + "gas_reports_include_tests": false, + "solc": null, + "auto_detect_solc": true, + "offline": false, + "optimizer": false, + "optimizer_runs": 200, + "optimizer_details": null, + "model_checker": null, + "verbosity": 0, + "eth_rpc_url": null, + "eth_rpc_jwt": null, + "eth_rpc_timeout": null, + "eth_rpc_headers": null, + "etherscan_api_key": null, + "ignored_error_codes": [ + "license", + "code-size", + "init-code-size", + "transient-storage" + ], + "ignored_warnings_from": [], + "deny_warnings": false, + "match_test": null, + "no_match_test": null, + "match_contract": null, + "no_match_contract": null, + "match_path": null, + "no_match_path": null, + "no_match_coverage": null, + "test_failures_file": "cache/test-failures", + "threads": null, + "show_progress": false, + "fuzz": { + "runs": 256, + "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, + "gas_report_samples": 256, + "failure_persist_dir": "cache/fuzz", + "failure_persist_file": "failures", + "no_zksync_reserved_addresses": false, + "show_logs": false, + "timeout": null + }, + "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, + "timeout": null, + "no_zksync_reserved_addresses": 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, + "fork_block_number": null, + "chain_id": null, + "gas_limit": 1073741824, + "code_size_limit": null, + "gas_price": null, + "block_base_fee_per_gas": 0, + "block_coinbase": "0x0000000000000000000000000000000000000000", + "block_timestamp": 1, + "block_difficulty": 0, + "block_prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "block_gas_limit": null, + "memory_limit": 134217728, + "extra_output": [], + "extra_output_files": [], + "names": false, + "sizes": false, + "via_ir": false, + "ast": false, + "rpc_storage_caching": { + "chains": "all", + "endpoints": "all" + }, + "no_storage_caching": false, + "no_rpc_rate_limit": false, + "use_literal_content": false, + "bytecode_hash": "ipfs", + "cbor_metadata": true, + "revert_strings": null, + "sparse_mode": false, + "build_info": false, + "build_info_path": null, + "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 + }, + "doc": { + "out": "docs", + "title": "", + "book": "book.toml", + "homepage": "README.md", + "ignore": [] + }, + "bind_json": { + "out": "utils/JsonBindings.sol", + "include": [], + "exclude": [] + }, + "fs_permissions": [ + { + "access": "read", + "path": "out" + } + ], + "isolate": false, + "disable_block_gas_limit": false, + "labels": {}, + "unchecked_cheatcode_artifacts": false, + "create2_library_salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "create2_deployer": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "vyper": {}, + "dependencies": null, + "soldeer": null, + "assertions_revert": true, + "legacy_assertions": false, + "odyssey": false, + "transaction_timeout": 120, + "eof": false, + "additional_compiler_profiles": [], + "compilation_restrictions": [], + "zksync": { + "compile": false, + "startup": false, + "zksolc": null, + "solc_path": null, + "hash_type": null, + "bytecode_hash": null, + "fallback_oz": false, + "enable_eravm_extensions": false, + "force_evmla": false, + "llvm_options": [], + "optimizer": true, + "optimizer_mode": "3", + "optimizer_details": null, + "suppressed_warnings": [], + "suppressed_errors": [] + } +} + +"#]]); +}); + +forgetest_init!(test_optimizer_config, |prj, cmd| { + // Default settings: optimizer disabled, optimizer runs 200. + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 200 +... + +"#]]); + + // Optimizer set to true: optimizer runs set to default value of 200. + let config = Config { optimizer: Some(true), ..Default::default() }; + prj.write_config(config); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 200 +... + +"#]]); + + // Optimizer runs set to 0: optimizer should be disabled, runs set to 0. + let config = Config { optimizer_runs: Some(0), ..Default::default() }; + prj.write_config(config); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 0 +... + +"#]]); + + // Optimizer runs set to 500: optimizer should be enabled, runs set to 500. + let config = Config { optimizer_runs: Some(500), ..Default::default() }; + prj.write_config(config); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 500 +... + +"#]]); + + // Optimizer disabled and runs set to 500: optimizer should be disabled, runs set to 500. + let config = Config { optimizer: Some(false), optimizer_runs: Some(500), ..Default::default() }; + prj.write_config(config); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 500 +... + +"#]]); + + // Optimizer enabled and runs set to 0: optimizer should be enabled, runs set to 0. + let config = Config { optimizer: Some(true), optimizer_runs: Some(0), ..Default::default() }; + prj.write_config(config); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 0 +... + +"#]]); +}); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index c840a80362..7f7c062991 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -1,4 +1,4 @@ -use foundry_common::fs; +use foundry_common::fs::{self, files_with_ext}; use foundry_test_utils::{ snapbox::{Data, IntoData}, TestCommand, TestProject, @@ -1691,3 +1691,63 @@ contract AContract { fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data()); } + +forgetest!(no_artifacts_written, |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; + } +} + "#, + ) + .unwrap(); + + 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(); + } +} + "#, + ) + .unwrap(); + + cmd.forge_fuse().arg("coverage").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) | +╰-------------------+---------------+---------------+---------------+---------------╯ +... +"#]]); + + // no artifacts are to be written + let files = files_with_ext(prj.artifacts(), "json").collect::>(); + + assert!(files.is_empty()); +}); diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 6a78f83231..fab7eb720a 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -168,7 +168,7 @@ Transaction: { "to": null, "maxFeePerGas": "0x77359401", "maxPriorityFeePerGas": "0x1", - "gas": "0x17575", + "gas": "0x241e7", "input": "[..]", "nonce": "0x0", "chainId": "0x7a69" @@ -222,7 +222,7 @@ ABI: [ "to": null, "maxFeePerGas": "0x77359401", "maxPriorityFeePerGas": "0x1", - "gas": "0x17575", + "gas": "0x241e7", "input": "[..]", "nonce": "0x0", "chainId": "0x7a69" diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index fdf69f101b..c4e68d3e46 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -26,6 +26,7 @@ mod svm; mod test_cmd; mod verify; mod verify_bytecode; +mod version; mod ext_integration; mod zk_build; diff --git a/crates/forge/tests/cli/odyssey.rs b/crates/forge/tests/cli/odyssey.rs index 49b8c01fc7..7d98e79fcf 100644 --- a/crates/forge/tests/cli/odyssey.rs +++ b/crates/forge/tests/cli/odyssey.rs @@ -11,7 +11,6 @@ forgetest_init!(test_eof_flag, |prj, cmd| { Compiler run successful with warnings: Warning (3805): This is a pre-release compiler version, please do not use it in production. - Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) [PASS] test_Increment() ([GAS]) diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 09df55668e..a2952ee1d1 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -4,8 +4,9 @@ use crate::constants::TEMPLATE_CONTRACT; use alloy_primitives::{address, hex, Address, Bytes}; use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; +use foundry_config::Config; use foundry_test_utils::{ - rpc, + rpc::{self, next_http_rpc_endpoint}, snapbox::IntoData, util::{OTHER_SOLC_VERSION, SOLC_VERSION}, ScriptOutcome, ScriptTester, @@ -230,7 +231,7 @@ Traces: ├─ [0] VM::startBroadcast() │ └─ ← [Return] ├─ [..] → new GasWaster@[..] - │ └─ ← [Return] 221 bytes of code + │ └─ ← [Return] 415 bytes of code ├─ [..] GasWaster::wasteGas(200000 [2e5]) │ └─ ← [Stop] └─ ← [Stop] @@ -242,10 +243,10 @@ Script ran successfully. ========================== Simulated On-chain Traces: - [44291] → new GasWaster@[..] - └─ ← [Return] 221 bytes of code + [..] → new GasWaster@[..] + └─ ← [Return] 415 bytes of code - [224] GasWaster::wasteGas(200000 [2e5]) + [..] GasWaster::wasteGas(200000 [2e5]) └─ ← [Stop] @@ -336,7 +337,7 @@ Traces: ├─ [0] VM::startBroadcast() │ └─ ← [Return] ├─ [..] → new GasWaster@[..] - │ └─ ← [Return] 221 bytes of code + │ └─ ← [Return] 415 bytes of code ├─ [..] GasWaster::wasteGas(200000 [2e5]) │ └─ ← [Stop] └─ ← [Stop] @@ -348,10 +349,10 @@ Script ran successfully. ========================== Simulated On-chain Traces: - [44291] → new GasWaster@[..] - └─ ← [Return] 221 bytes of code + [..] → new GasWaster@[..] + └─ ← [Return] 415 bytes of code - [224] GasWaster::wasteGas(200000 [2e5]) + [..] GasWaster::wasteGas(200000 [2e5]) └─ ← [Stop] @@ -520,7 +521,7 @@ Traces: ├─ [0] VM::startBroadcast() │ └─ ← [Return] ├─ [..] → new HashChecker@[..] - │ └─ ← [Return] 368 bytes of code + │ └─ ← [Return] 718 bytes of code └─ ← [Stop] @@ -1925,6 +1926,7 @@ forgetest_async!(adheres_to_json_flag, |prj, cmd| { } foundry_test_utils::util::initialize(prj.root()); + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_script( "Foo", r#" @@ -2372,9 +2374,9 @@ Traces: ├─ [0] VM::startBroadcast() │ └─ ← [Return] ├─ [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 - │ └─ ← [Return] 116 bytes of code + │ └─ ← [Return] 175 bytes of code ├─ [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 - │ ├─ [145] A::getValue() [staticcall] + │ ├─ [..] A::getValue() [staticcall] │ │ └─ ← [Return] 100 │ └─ ← [Return] 62 bytes of code └─ ← [Stop] @@ -2386,11 +2388,11 @@ Script ran successfully. ========================== Simulated On-chain Traces: - [23273] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 - └─ ← [Return] 116 bytes of code + [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 + └─ ← [Return] 175 bytes of code - [15662] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 - ├─ [145] A::getValue() [staticcall] + [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 + ├─ [..] A::getValue() [staticcall] │ └─ ← [Return] 100 └─ ← [Return] 62 bytes of code ... @@ -2443,3 +2445,180 @@ contract ContractScript is Script { assert_eq!(sequence.transactions.len(), 2); assert_eq!(sequence.transactions[1].additional_contracts.len(), 1); }); + +// +forgetest_async!(should_set_correct_sender_nonce_via_cli, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "MyScript.s.sol", + r#" + import {Script, console} from "forge-std/Script.sol"; + + contract MyScript is Script { + function run() public view { + console.log("sender nonce", vm.getNonce(msg.sender)); + } + } + "#, + ) + .unwrap(); + + let rpc_url = next_http_rpc_endpoint(); + + let fork_bn = 21614115; + + cmd.forge_fuse() + .args([ + "script", + "MyScript", + "--sender", + "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97", + "--fork-block-number", + &fork_bn.to_string(), + "--rpc-url", + &rpc_url, + ]) + .assert_success() + .stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +== Logs == + sender nonce 1124703[..]"#]]); +}); + +forgetest_async!(dryrun_without_broadcast, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract Called { + event log_string(string); + uint256 public x; + uint256 public y; + function run(uint256 _x, uint256 _y) external { + x = _x; + y = _y; + emit log_string("script ran"); + } +} + +contract DryRunTest is Script { + function run() external { + vm.startBroadcast(); + Called called = new Called(); + called.run(123, 456); + } +} + "#, + ) + .unwrap(); + + cmd.arg("script") + .args([ + "DryRunTest", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "-vvvv", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] DryRunTest::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3 + │ └─ ← [Return] 567 bytes of code + ├─ [..] Called::run(123, 456) + │ ├─ emit log_string(val: "script ran") + │ └─ ← [Stop] + └─ ← [Stop] + + +Script ran successfully. + +== Logs == + script ran + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [113557] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3 + └─ ← [Return] 567 bytes of code + + [46595] Called::run(123, 456) + ├─ emit log_string(val: "script ran") + └─ ← [Stop] + + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + +=== Transactions that will be broadcast === + + +Chain 31337 + +### Transaction 1 ### + +accessList [] +chainId 31337 +gasLimit 228247 +gasPrice +input [..] +maxFeePerBlobGas +maxFeePerGas +maxPriorityFeePerGas +nonce 0 +to +type 0 +value 0 + +### Transaction 2 ### + +accessList [] +chainId 31337 +gasLimit 93856 +gasPrice +input 0x7357f5d2000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8 +maxFeePerBlobGas +maxFeePerGas +maxPriorityFeePerGas +nonce 1 +to 0x5FbDB2315678afecb367f032d93F642f64180aa3 +type 0 +value 0 +contract: Called(0x5FbDB2315678afecb367f032d93F642f64180aa3) +data (decoded): run(uint256,uint256)( + 123, + 456 +) + + +SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 20f865fc9e..36b8d514e4 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -291,7 +291,7 @@ contract SimpleContractTest is DSTest { function test() public { SimpleContract c = new SimpleContract(); c.setValues(100); - console.log("Value set: ", 100); + console.logUint(100); } } "#; @@ -591,6 +591,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/6579 forgetest_init!(include_custom_types_in_traces, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.wipe_contracts(); prj.add_test( @@ -958,6 +959,7 @@ contract SetupFailureTest is Test { // https://github.com/foundry-rs/foundry/issues/7530 forgetest_init!(should_show_precompile_labels, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.wipe_contracts(); prj.add_test( @@ -1214,9 +1216,6 @@ forgetest_init!(internal_functions_trace, |prj, cmd| { prj.wipe_contracts(); prj.clear(); - // Disable optimizer because for simple contract most functions will get inlined. - prj.write_config(Config { optimizer: false, ..Default::default() }); - prj.add_test( "Simple", r#" @@ -1264,7 +1263,7 @@ Compiler run successful! Ran 1 test for test/Simple.sol:SimpleContractTest [PASS] test() ([GAS]) Traces: - [244864] SimpleContractTest::test() + [..] SimpleContractTest::test() ├─ [165406] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 826 bytes of code ├─ [22630] SimpleContract::increment() @@ -1292,9 +1291,6 @@ forgetest_init!(internal_functions_trace_memory, |prj, cmd| { prj.wipe_contracts(); prj.clear(); - // Disable optimizer because for simple contract most functions will get inlined. - prj.write_config(Config { optimizer: false, ..Default::default() }); - prj.add_test( "Simple", r#" @@ -1322,11 +1318,10 @@ contract SimpleContractTest is Test { "#, ) .unwrap(); - cmd.args(["test", "-vvvv", "--decode-internal", "test"]).assert_success().stdout_eq(str![[ - r#" + cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#" ... Traces: - [406629] SimpleContractTest::test() + [..] SimpleContractTest::test() ├─ [370554] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 1737 bytes of code ├─ [2511] SimpleContract::setStr("new value") @@ -1335,8 +1330,7 @@ Traces: │ └─ ← [Stop] └─ ← [Stop] ... -"# - ]]); +"#]]); }); // tests that `forge test` with a seed produces deterministic random values for uint and addresses. @@ -1423,6 +1417,7 @@ 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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.wipe_contracts(); prj.add_test( @@ -1508,6 +1503,7 @@ 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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.wipe_contracts(); prj.add_test( @@ -1586,6 +1582,7 @@ contract ATest is Test { // tests `pauseTracing` and `resumeTracing` functions #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(pause_tracing, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); @@ -2277,13 +2274,6 @@ Use --match-contract and --match-path to further limit the search. "#]]); }); -forgetest_init!(deprecated_regex_arg, |prj, cmd| { - cmd.args(["test", "--decode-internal", "test_Increment"]).assert_success().stderr_eq(str![[r#" -Warning: specifying argument for --decode-internal is deprecated and will be removed in the future, use --match-test instead - -"#]]); -}); - // Test a script that calls vm.rememberKeys forgetest_init!(script_testing, |prj, cmd| { prj @@ -2341,6 +2331,7 @@ Logs: // forgetest_init!(metadata_bytecode_traces, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_source( "ParentProxy.sol", r#" @@ -2452,17 +2443,209 @@ contract Dummy { let dump_path = prj.root().join("dump.json"); - cmd.args(["test", "--debug", "testDummy", "--dump", dump_path.to_str().unwrap()]); + cmd.args(["test", "--mt", "testDummy", "--debug", "--dump", dump_path.to_str().unwrap()]); cmd.assert_success(); assert!(dump_path.exists()); }); +forgetest_init!(test_assume_no_revert_with_data, |prj, cmd| { + let config = Config { + fuzz: { FuzzConfig { runs: 60, seed: Some(U256::from(100)), ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + + prj.add_source( + "AssumeNoRevertTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +interface Vm { + struct PotentialRevert { + address reverter; + bool partialMatch; + bytes revertData; + } + function expectRevert() external; + function assumeNoRevert() external pure; + function assumeNoRevert(PotentialRevert calldata revertData) external pure; + function assumeNoRevert(PotentialRevert[] calldata revertData) external pure; + function expectRevert(bytes4 revertData, uint64 count) external; +} + +contract ReverterB { + /// @notice has same error selectors as contract below to test the `reverter` param + error MyRevert(); + error SpecialRevertWithData(uint256 x); + + function revertIf2(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithData() public pure returns (bool) { + revert SpecialRevertWithData(2); + } +} + +contract Reverter { + error MyRevert(); + error RevertWithData(uint256 x); + error UnusedError(); + error ExpectedRevertCountZero(); + + ReverterB public immutable subReverter; + + constructor() { + subReverter = new ReverterB(); + } + + function myFunction() public pure returns (bool) { + revert MyRevert(); + } + + function revertIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithDataIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert RevertWithData(2); + } + return true; + } + + function twoPossibleReverts(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } else if (x == 3) { + revert RevertWithData(3); + } + return true; + } + + function revertIf2Or3ExpectedRevertZero(uint256 x) public pure returns (bool) { + if (x == 2) { + revert ExpectedRevertCountZero(); + } else if (x == 3) { + revert MyRevert(); + } + return true; + } +} + +contract ReverterTest is Test { + Reverter reverter; + Vm _vm = Vm(VM_ADDRESS); + + function setUp() public { + reverter = new Reverter(); + } + + /// @dev Test that `assumeNoRevert` does not reject an unanticipated error selector + function testAssume_wrongSelector_fails(uint256 x) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.UnusedError.selector), partialMatch: false, reverter: address(0)})); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` does not reject an unanticipated error with extra data + function testAssume_wrongData_fails(uint256 x) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 3), partialMatch: false, reverter: address(0)})); + reverter.revertWithDataIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects an error selector from a different contract + function testAssumeWithReverter_fails(uint256 x) public view { + ReverterB subReverter = (reverter.subReverter()); + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)})); + subReverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects one of two different error selectors when supplying a specific reverter + function testMultipleAssumes_OneWrong_fails(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)}); + revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)}); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } + + /// @dev Test that `assumeNoRevert` assumptions are cleared after the first non-cheatcode external call + function testMultipleAssumesClearAfterCall_fails(uint256 x) public view { + 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)}); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + + reverter.twoPossibleReverts(2); + } + + /// @dev Test that `assumeNoRevert` correctly rejects a generic assumeNoRevert call after any specific reason is provided + function testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4 selector) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encode(selector), partialMatch: false, reverter: address(0)})); + _vm.assumeNoRevert(); + reverter.twoPossibleReverts(2); + } + + function testAssumeThenExpectCountZeroFails(uint256 x) public { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0); + reverter.revertIf2Or3ExpectedRevertZero(x); + } + + function testExpectCountZeroThenAssumeFails(uint256 x) public { + _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0); + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertIf2Or3ExpectedRevertZero(x); + } + +}"#, + ) + .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: 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: 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]) +... + +"#]]); +}); + forgetest_async!(can_get_broadcast_txs, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); let (_api, handle) = spawn(NodeConfig::test().silent()).await; + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.insert_vm(); prj.insert_ds_test(); prj.insert_console(); @@ -2701,7 +2884,7 @@ contract ForkTest is Test { 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] test_fork_err_message() ([GAS]) +[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] ... @@ -2711,6 +2894,8 @@ 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.write_config(Config { optimizer: Some(true), ..Default::default() }); + cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" ... Ran 1 test for test/Counter.t.sol:CounterTest @@ -2770,6 +2955,7 @@ Encountered a total of 1 failing tests, 0 tests succeeded // 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.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_test( "DebugTraceRecordingTest.t.sol", r#" @@ -2797,7 +2983,7 @@ Compiler run successful! Ran 1 test for test/DebugTraceRecordingTest.t.sol:DebugTraceRecordingTest [PASS] test_start_stop_recording() ([GAS]) Traces: - [476338] DebugTraceRecordingTest::test_start_stop_recording() + [..] DebugTraceRecordingTest::test_start_stop_recording() └─ ← [Stop] Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] @@ -2834,3 +3020,10 @@ forgetest!(test_fail_deprecation_warning, |prj, cmd| { .stderr_eq(r#"Warning: `testFail*` has been deprecated and will be removed in the next release. Consider changing to test_Revert[If|When]_Condition and expecting a revert. Found deprecated testFail* function(s): testFail_deprecated, testFail_deprecated2. "#); }); + +#[cfg(not(feature = "isolate-by-default"))] +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]); +}); diff --git a/crates/forge/tests/cli/utils.rs b/crates/forge/tests/cli/utils.rs index 2b0bb62731..058390dceb 100644 --- a/crates/forge/tests/cli/utils.rs +++ b/crates/forge/tests/cli/utils.rs @@ -109,6 +109,16 @@ impl EnvExternalities { }) } + pub fn sepolia_empty_verifier() -> Option { + Some(Self { + chain: NamedChain::Sepolia, + rpc: network_rpc_key("sepolia")?, + pk: network_private_key("sepolia")?, + etherscan: String::new(), + verifier: String::new(), + }) + } + /// Returns the arguments required to deploy the contract pub fn create_args(&self) -> Vec { vec![ diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 60a7944773..f4465834eb 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -91,6 +91,29 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul }) } +fn verify_check( + guid: String, + chain: String, + etherscan_api_key: Option, + verifier: Option, + mut cmd: TestCommand, +) { + let mut args = vec!["verify-check", &guid, "--chain-id", &chain]; + + if let Some(etherscan_api_key) = ðerscan_api_key { + args.push("--etherscan-api-key"); + args.push(etherscan_api_key); + } + + if let Some(verifier) = &verifier { + args.push("--verifier"); + args.push(verifier); + } + cmd.forge_fuse().args(args); + + parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +} + fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { let guid = { // Give Etherscan some time to detect the transaction. @@ -110,17 +133,29 @@ fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { }; // verify-check - cmd.forge_fuse() - .arg("verify-check") - .arg(guid) - .arg("--chain-id") - .arg(info.chain.to_string()) - .arg("--etherscan-api-key") - .arg(info.etherscan) - .arg("--verifier") - .arg(info.verifier); + let etherscan = (!info.etherscan.is_empty()).then_some(info.etherscan.clone()); + let verifier = (!info.verifier.is_empty()).then_some(info.verifier.clone()); + verify_check(guid, info.chain.to_string(), etherscan, verifier, cmd); +} - parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +fn deploy_contract( + info: &EnvExternalities, + contract_path: &str, + prj: TestProject, + cmd: &mut TestCommand, +) -> String { + add_unique(&prj); + add_verify_target(&prj); + let output = cmd + .forge_fuse() + .arg("create") + .args(info.create_args()) + .arg(contract_path) + .assert_success() + .get_output() + .stdout_lossy(); + utils::parse_deployed_address(output.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {output}")) } #[allow(clippy::disallowed_macros)] @@ -128,30 +163,27 @@ fn verify_on_chain(info: Option, prj: TestProject, mut cmd: Te // only execute if keys present if let Some(info) = info { println!("verifying on {}", info.chain); - add_unique(&prj); - add_verify_target(&prj); let contract_path = "src/Verify.sol:Verify"; - let output = cmd - .arg("create") - .args(info.create_args()) - .arg(contract_path) - .assert_success() - .get_output() - .stdout_lossy(); - let address = utils::parse_deployed_address(output.as_str()) - .unwrap_or_else(|| panic!("Failed to parse deployer {output}")); + let address = deploy_contract(&info, contract_path, prj, &mut cmd); - cmd.forge_fuse().arg("verify-contract").root_arg().args([ + let mut args = vec![ "--chain-id".to_string(), info.chain.to_string(), address, contract_path.to_string(), - "--etherscan-api-key".to_string(), - info.etherscan.to_string(), - "--verifier".to_string(), - info.verifier.to_string(), - ]); + ]; + + if !info.etherscan.is_empty() { + args.push("--etherscan-api-key".to_string()); + args.push(info.etherscan.clone()); + } + + if !info.verifier.is_empty() { + args.push("--verifier".to_string()); + args.push(info.verifier.clone()); + } + cmd.forge_fuse().arg("verify-contract").root_arg().args(args); await_verification_response(info, cmd) } @@ -247,3 +279,8 @@ forgetest!(can_create_verify_random_contract_sepolia, |prj, cmd| { forgetest!(can_guess_constructor_args, |prj, cmd| { guess_constructor_args(EnvExternalities::goerli(), prj, cmd); }); + +// tests `create && verify-contract && verify-check` on sepolia with default sourcify verifier +forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| { + verify_on_chain(EnvExternalities::sepolia_empty_verifier(), prj, cmd); +}); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 6e89f1e940..127765bbfd 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -25,7 +25,7 @@ fn test_verify_bytecode( // fetch and flatten source code let source_code = cmd .cast_fuse() - .args(["etherscan-source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) + .args(["source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) .assert_success() .get_output() .stdout_lossy(); @@ -81,7 +81,7 @@ fn test_verify_bytecode_with_ignore( let source_code = cmd .cast_fuse() .args([ - "etherscan-source", + "source", addr, "--flatten", "--etherscan-api-key", @@ -144,8 +144,8 @@ forgetest_async!(can_verify_bytecode_no_metadata, |prj, cmd| { None, Config { evm_version: EvmVersion::London, - optimizer_runs: 999999, - optimizer: true, + optimizer_runs: Some(999999), + optimizer: Some(true), cbor_metadata: false, bytecode_hash: BytecodeHash::None, ..Default::default() @@ -165,8 +165,8 @@ forgetest_async!(can_verify_bytecode_with_metadata, |prj, cmd| { None, Config { evm_version: EvmVersion::Paris, - optimizer_runs: 50000, - optimizer: true, + optimizer_runs: Some(50000), + optimizer: Some(true), ..Default::default() }, "etherscan", @@ -185,8 +185,8 @@ forgetest_async!(can_verify_bytecode_with_blockscout, |prj, cmd| { None, Config { evm_version: EvmVersion::London, - optimizer: true, - optimizer_runs: 200, + optimizer: Some(true), + optimizer_runs: Some(200), ..Default::default() }, "blockscout", @@ -205,8 +205,8 @@ forgetest_async!(can_vb_create2_with_blockscout, |prj, cmd| { None, Config { evm_version: EvmVersion::London, - optimizer_runs: 999999, - optimizer: true, + optimizer_runs: Some(999999), + optimizer: Some(true), cbor_metadata: false, bytecode_hash: BytecodeHash::None, ..Default::default() @@ -232,8 +232,8 @@ forgetest_async!(can_verify_bytecode_with_constructor_args, |prj, cmd| { Some(constructor_args), Config { evm_version: EvmVersion::London, - optimizer: true, - optimizer_runs: 200, + optimizer: Some(true), + optimizer_runs: Some(200), ..Default::default() }, "etherscan", @@ -251,8 +251,8 @@ forgetest_async!(can_ignore_creation, |prj, cmd| { "SystemConfig", Config { evm_version: EvmVersion::London, - optimizer_runs: 999999, - optimizer: true, + optimizer_runs: Some(999999), + optimizer: Some(true), cbor_metadata: false, bytecode_hash: BytecodeHash::None, ..Default::default() @@ -273,8 +273,8 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| { "SystemConfig", Config { evm_version: EvmVersion::London, - optimizer_runs: 999999, - optimizer: true, + optimizer_runs: Some(999999), + optimizer: Some(true), cbor_metadata: false, bytecode_hash: BytecodeHash::None, ..Default::default() @@ -298,7 +298,7 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| { // "WETH9", // Config { // evm_version: EvmVersion::default(), -// optimizer: true, +// optimizer: Some(true), // optimizer_runs: 10000, // cbor_metadata: true, // bytecode_hash: BytecodeHash::Bzzr1, diff --git a/crates/forge/tests/cli/version.rs b/crates/forge/tests/cli/version.rs new file mode 100644 index 0000000000..0df88b83a5 --- /dev/null +++ b/crates/forge/tests/cli/version.rs @@ -0,0 +1,18 @@ +use foundry_test_utils::{forgetest, str}; + +forgetest!(print_short_version, |_prj, cmd| { + cmd.arg("-V").assert_success().stdout_eq(str![[r#" +forge [..]-[..] ([..] [..]) + +"#]]); +}); + +forgetest!(print_long_version, |_prj, cmd| { + cmd.arg("--version").assert_success().stdout_eq(str![[r#" +forge Version: [..] +Commit SHA: [..] +Build Timestamp: [..] +Build Profile: [..] + +"#]]); +}); diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json index 81803d949d..20324950fc 100644 --- a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -12,11 +12,11 @@ "topics": [ "0x41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000f56616c7565207365743a20203130300000000000000000000000000000000000" + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000033130300000000000000000000000000000000000000000000000000000000000" } ], "decoded_logs": [ - "Value set: 100" + "100" ], "kind": { "Unit": { diff --git a/crates/forge/tests/fixtures/colored_traces.svg b/crates/forge/tests/fixtures/colored_traces.svg new file mode 100644 index 0000000000..96726b528e --- /dev/null +++ b/crates/forge/tests/fixtures/colored_traces.svg @@ -0,0 +1,82 @@ + + + + + + + No files changed, compilation skipped + + + + Ran 1 test for test/Counter.t.sol:CounterTest + + [PASS] test_Increment() ([GAS]) + + Traces: + + [137242] CounterTest::setUp() + + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + + │ └─ ← [Return] 481 bytes of code + + ├─ [2592] Counter::setNumber(0) + + │ └─ ← [Stop] + + └─ ← [Stop] + + + + [31851] 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] + + ├─ storage changes: + + │ @ 0: 0 → 1 + + └─ ← [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) + + + + + + diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 8b49d4acca..30b34dcd9d 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -7,6 +7,7 @@ use forge::{ fuzz::CounterExample, result::{SuiteResult, TestStatus}, }; +use foundry_config::Config; use foundry_test_utils::{forgetest_init, str, Filter}; use std::collections::BTreeMap; @@ -117,6 +118,7 @@ async fn test_persist_fuzz_failure() { () => { run_fail!(|config| {}) }; (|$config:ident| $e:expr) => {{ let mut runner = TEST_DATA_DEFAULT.runner_with(|$config| { + $config.optimizer = Some(true); $config.fuzz.runs = 1000; $e }); @@ -161,6 +163,7 @@ async fn test_persist_fuzz_failure() { } forgetest_init!(test_can_scrape_bytecode, |prj, cmd| { + prj.write_config(Config { optimizer: Some(true), ..Default::default() }); prj.add_source( "FuzzerDict.sol", r#" diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index eab7f9ec1b..991c556c7d 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -7,7 +7,9 @@ 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 mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.optimizer = Some(true); + }); let result = runner.test_collect(&filter); let results = result .into_iter() diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index b07f056abf..ec29314ecf 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -263,6 +263,7 @@ 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)); + config.optimizer = Some(true); }); match get_counterexample!(runner, &filter) { @@ -686,24 +687,6 @@ async fn test_invariant_after_invariant() { ); } -#[tokio::test(flavor = "multi_thread")] -async fn test_invariant_selectors_weight() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSelectorsWeight.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 = 10; - }); - let results = runner.test_collect(&filter); - assert_multiple( - &results, - BTreeMap::from([( - "default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol:InvariantSelectorsWeightTest", - vec![("invariant_selectors_weight()", true, None, None, None)], - )]), - ) -} - #[tokio::test(flavor = "multi_thread")] async fn test_no_reverts_in_counterexample() { let filter = @@ -795,6 +778,7 @@ contract AssumeTest is Test { // forgetest_init!(should_revert_with_assume_code, |prj, cmd| { let config = Config { + optimizer: Some(true), invariant: { InvariantConfig { fail_on_revert: true, max_assume_rejects: 10, ..Default::default() } }, @@ -1013,3 +997,80 @@ 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.write_config(Config { + optimizer: Some(true), + invariant: { InvariantConfig { runs: 1, depth: 10, ..Default::default() } }, + ..Default::default() + }); + 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; + } +} + "#, + ) + .unwrap(); + + 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(), 3); + assertEq(handlerTwo.hit4(), 1); + assertEq(handlerTwo.hit5(), 2); + } + + function invariant_selectors_weight() public view {} +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--fuzz-seed", "119", "--mt", "invariant_selectors_weight"]).assert_success(); +}); diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index ebcf30e211..7fc5bf6271 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -5,12 +5,12 @@ use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; use alloy_json_abi::Event; use alloy_primitives::{address, b256, Address, U256}; use forge::{ + constants::HARDHAT_CONSOLE_ADDRESS, decode::decode_console_logs, result::{TestKind, TestStatus}, }; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; use foundry_evm::traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}; -use foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS; use foundry_test_utils::Filter; use std::sync::Arc; @@ -391,3 +391,9 @@ 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); diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 2ed1000617..82bdb6bc23 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -116,6 +116,9 @@ impl ForgeTestProfile { 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; diff --git a/crates/forge/tests/it/zk/cheats.rs b/crates/forge/tests/it/zk/cheats.rs index 2fb3c21b18..5533394b6b 100644 --- a/crates/forge/tests/it/zk/cheats.rs +++ b/crates/forge/tests/it/zk/cheats.rs @@ -79,6 +79,29 @@ async fn test_zk_cheat_expect_emit_works() { TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_expect_revert_works() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("test(ExpectRevert$|FailExpectRevert)", "ZkCheatcodesTest", ".*"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_cheat_expect_revert_works_with_internal_reverts() { + let mut runner = TEST_DATA_DEFAULT.runner_zksync(); + let mut config = runner.config.as_ref().clone(); + config.allow_internal_expect_revert = true; + runner.config = std::sync::Arc::new(config); + let filter = Filter::new( + "testExpectRevertDeeperDepthsWithInternalRevertsEnabled", + "ZkCheatcodesTest", + ".*", + ); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; +} + #[tokio::test(flavor = "multi_thread")] async fn test_zk_cheat_expect_call_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); @@ -140,7 +163,12 @@ async fn test_zk_record_logs() { #[tokio::test(flavor = "multi_thread")] async fn test_zk_cheatcodes_in_zkvm() { - let runner = TEST_DATA_DEFAULT.runner_zksync(); + let mut runner = TEST_DATA_DEFAULT.runner_zksync(); + let mut config = runner.config.as_ref().clone(); + // This is now false by default so in order to expect a revert from an internal call, we need to + // set it to true https://github.com/foundry-rs/foundry/pull/9537 + config.allow_internal_expect_revert = true; + runner.config = std::sync::Arc::new(config); let filter = Filter::new(".*", "ZkCheatcodesInZkVmTest", ".*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; @@ -158,6 +186,7 @@ forgetest_async!(test_zk_use_factory_dep, |prj, cmd| { setup_deploy_prj(&mut prj); cmd.forge_fuse(); + // We added the optimizer flag which is now false by default so we need to set it to true run_zk_script_test( prj.root(), &mut cmd, @@ -165,7 +194,7 @@ forgetest_async!(test_zk_use_factory_dep, |prj, cmd| { "DeployCounterWithBytecodeHash", Some("transmissions11/solmate@v7 OpenZeppelin/openzeppelin-contracts cyfrin/zksync-contracts"), 2, - Some(&["-vvvvv", "--via-ir", "--system-mode", "true", "--broadcast"]), + Some(&["-vvvvv", "--via-ir", "--system-mode", "true", "--broadcast", "--optimize", "true"]), ).await; }); diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index 714ec931f7..1ae90b0ec3 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -79,7 +79,16 @@ async fn test_zk_contract_create2() { prj.add_test("Create2.t.sol", include_str!("../../fixtures/zk/Create2.t.sol")).unwrap(); - cmd.args(["test", "--zk-startup", "--evm-version", "shanghai", "--mc", "Create2Test"]); + cmd.args([ + "test", + "--zk-startup", + "--evm-version", + "shanghai", + "--mc", + "Create2Test", + "--optimize", + "true", + ]); cmd.assert_success().get_output().stdout_lossy().contains("Suite result: ok"); } diff --git a/crates/forge/tests/it/zk/gas.rs b/crates/forge/tests/it/zk/gas.rs index 916068e80b..a6b42718b4 100644 --- a/crates/forge/tests/it/zk/gas.rs +++ b/crates/forge/tests/it/zk/gas.rs @@ -79,10 +79,39 @@ forgetest_async!(zk_script_execution_with_gas_per_pubdata, |prj, cmd| { let private_key = get_rich_wallet_key(); // Test with unacceptable gas per pubdata (should fail) - let zero_pubdata_args = create_script_args(&private_key, &url, "--zk-gas-per-pubdata", "1"); - cmd.arg("script").args(&zero_pubdata_args); - cmd.assert_failure(); - cmd.forge_fuse(); + let mut forge_bin = prj.forge_bin(); + // We had to change the approach of testing an invalid gas per pubdata value because there were + // changes upstream for the timeout and retries mechanism Now we execute the command + // directly and check the output with a manual timeout. The previous approach was to use the + // `forge script` command with a timeout but now it's not timeouting anymore for this error. + let mut child = forge_bin + .args([ + "script", + "--zksync", + "script/Gas.s.sol:GasScript", + "--private-key", + &private_key, + "--chain", + "260", + "--rpc-url", + &url, + "--slow", + "-vvvvv", + "--broadcast", + "--zk-gas-per-pubdata", + "1", + ]) + .current_dir(prj.root()) + .spawn() + .expect("failed to spawn process"); + + // Wait for 10 seconds then kill the process + std::thread::sleep(std::time::Duration::from_secs(10)); + child.kill().expect("failed to kill process"); + let output = child.wait().expect("failed to wait for process"); + + // Assert command was killed + assert!(!output.success()); // Test with sufficient gas per pubdata (should succeed) let sufficient_pubdata_args = diff --git a/crates/forge/tests/it/zk/paymaster.rs b/crates/forge/tests/it/zk/paymaster.rs index abfc54d2ba..dc3f69782e 100644 --- a/crates/forge/tests/it/zk/paymaster.rs +++ b/crates/forge/tests/it/zk/paymaster.rs @@ -32,7 +32,15 @@ async fn test_zk_contract_paymaster() { prj.add_source("MyPaymaster.sol", include_str!("../../fixtures/zk/MyPaymaster.sol")).unwrap(); prj.add_source("Paymaster.t.sol", include_str!("../../fixtures/zk/Paymaster.t.sol")).unwrap(); - cmd.args(["test", "--zk-startup", "--via-ir", "--match-contract", "TestPaymasterFlow"]); + cmd.args([ + "test", + "--zk-startup", + "--via-ir", + "--match-contract", + "TestPaymasterFlow", + "--optimize", + "true", + ]); assert!(cmd.assert_success().get_output().stdout_lossy().contains("Suite result: ok")); } @@ -128,6 +136,7 @@ forgetest_async!(test_zk_deploy_with_paymaster, |prj, cmd| { forgetest_async!(paymaster_script_test, |prj, cmd| { setup_deploy_prj(&mut prj); cmd.forge_fuse(); + // We added the optimizer flag which is now false by default so we need to set it to true run_zk_script_test( prj.root(), &mut cmd, @@ -135,7 +144,7 @@ forgetest_async!(paymaster_script_test, |prj, cmd| { "PaymasterScript", Some("OpenZeppelin/openzeppelin-contracts cyfrin/zksync-contracts"), 3, - Some(&["-vvvvv", "--via-ir"]), + Some(&["-vvvvv", "--via-ir", "--optimize", "true"]), ) .await; }); diff --git a/crates/forge/tests/it/zk/traces.rs b/crates/forge/tests/it/zk/traces.rs index 4132e0ba27..054aaa7d36 100644 --- a/crates/forge/tests/it/zk/traces.rs +++ b/crates/forge/tests/it/zk/traces.rs @@ -31,8 +31,8 @@ const VALUE_TEN: Bytes = Bytes::from_static( hex!("000000000000000000000000000000000000000000000000000000000000000a").as_slice(), ); const VALUE_LOG_UINT_TEN: Bytes = Bytes::from_static( - hex!("f5b1bba9000000000000000000000000000000000000000000000000000000000000000a").as_slice(), -); // selector: log(uint) + hex!("f82c50f1000000000000000000000000000000000000000000000000000000000000000a").as_slice(), +); // selector: log(uint256) static BYTECODE_ADDER: LazyLock> = LazyLock::new(|| get_zk_artifact_bytecode("Trace.t.sol/Adder.json")); diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 0771f26335..460b478c5c 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -4,7 +4,7 @@ use crate::{ }; use alloy_chains::Chain; use alloy_consensus::TxEnvelope; -use alloy_eips::eip2718::Encodable2718; +use alloy_eips::{eip2718::Encodable2718, BlockId}; use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; use alloy_primitives::{ map::{AddressHashMap, AddressHashSet}, @@ -55,10 +55,16 @@ where Ok(()) } -pub async fn next_nonce(caller: Address, provider_url: &str) -> eyre::Result { +pub async fn next_nonce( + caller: Address, + provider_url: &str, + block_number: Option, +) -> eyre::Result { let provider = try_get_http_provider(provider_url) .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; - Ok(provider.get_transaction_count(caller).await?) + + let block_id = block_number.map_or(BlockId::latest(), BlockId::number); + Ok(provider.get_transaction_count(caller).block_id(block_id).await?) } #[allow(clippy::too_many_arguments)] diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 9c10d83c41..680f20d15b 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -33,7 +33,7 @@ use foundry_evm::{ }; use futures::future::join_all; use itertools::Itertools; -use std::path::PathBuf; +use std::path::Path; use yansi::Paint; /// State after linking, contains the linked build data along with library addresses and optional @@ -497,7 +497,7 @@ impl PreSimulationState { Ok(()) } - pub fn run_debug_file_dumper(self, path: &PathBuf) -> Result<()> { + pub fn dump_debugger(self, path: &Path) -> Result<()> { self.create_debugger().dump_to_file(path)?; Ok(()) } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 4b4d17a321..b81d00974d 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -227,7 +227,7 @@ impl ScriptArgs { pub async fn preprocess(self) -> Result { let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); - let (config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + let (config, mut evm_opts) = self.load_config_and_evm_opts()?; if let Some(sender) = self.maybe_load_private_key()? { evm_opts.sender = sender; @@ -265,7 +265,7 @@ impl ScriptArgs { if pre_simulation.args.debug { return match pre_simulation.args.dump.clone() { - Some(ref path) => pre_simulation.run_debug_file_dumper(path), + Some(path) => pre_simulation.dump_debugger(&path), None => pre_simulation.run_debugger(), }; } @@ -308,6 +308,11 @@ impl ScriptArgs { // Exit early in case user didn't provide any broadcast/verify related flags. if !bundled.args.should_broadcast() { if !shell::is_json() { + if shell::verbosity() >= 4 { + sh_println!("\n=== Transactions that will be broadcast ===\n")?; + 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.")?; } return Ok(()); @@ -557,7 +562,7 @@ pub struct ScriptConfig { 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).await? + next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? } else { // dapptools compatibility 1 @@ -568,7 +573,7 @@ impl ScriptConfig { pub async fn update_sender(&mut self, sender: Address) -> Result<()> { self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { - next_nonce(sender, fork_url).await? + next_nonce(sender, fork_url, None).await? } else { // dapptools compatibility 1 @@ -653,8 +658,7 @@ impl ScriptConfig { &self.config, self.evm_opts.clone(), Some(known_contracts), - Some(target.name), - Some(target.version), + Some(target), strategy .runner .new_cheatcode_inspector_strategy(strategy.context.as_ref()), @@ -717,7 +721,7 @@ mod tests { "--etherscan-api-key", "goerli", ]); - let config = args.load_config(); + let config = args.load_config().unwrap(); assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); } @@ -782,7 +786,7 @@ mod tests { root.as_os_str().to_str().unwrap(), ]); - let config = args.load_config(); + 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())); } diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index ee1c7b46c8..605cdf9ddb 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -2,8 +2,8 @@ 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::Result; -use foundry_common::{provider::RetryProvider, shell}; +use eyre::{eyre, Result}; +use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell}; use std::time::Duration; /// Convenience enum for internal signalling of transaction status @@ -30,39 +30,28 @@ pub async fn check_tx_status( hash: TxHash, timeout: u64, ) -> (TxHash, Result) { - // We use the inner future so that we can use ? operator in the future, but - // still neatly return the tuple - let result = async move { - // First check if there's a receipt - let receipt_opt = provider.get_transaction_receipt(hash).await?; - if let Some(receipt) = receipt_opt { - return Ok(receipt.into()); - } - - loop { + let result = retry::Retry::new_no_delay(3) + .run_async_until_break(|| async { match PendingTransactionBuilder::new(provider.clone(), hash) .with_timeout(Some(Duration::from_secs(timeout))) .get_receipt() .await { - Ok(receipt) => return Ok(receipt.into()), - // do nothing on timeout, we will check whether tx is dropped below - Err(PendingTransactionError::TxWatcher(WatchTxError::Timeout)) => {} - // treat other errors as fatal - Err(e) => return Err(e.into()), - } - - if provider.get_transaction_by_hash(hash).await?.is_some() { - trace!("tx is still known to the node, waiting for receipt"); - } else { - trace!("eth_getTransactionByHash returned null, assuming dropped"); - break + Ok(receipt) => Ok(receipt.into()), + Err(e) => match provider.get_transaction_by_hash(hash).await { + Ok(_) => match e { + PendingTransactionError::TxWatcher(WatchTxError::Timeout) => { + Err(RetryError::Continue(eyre!( + "tx is still known to the node, waiting for receipt" + ))) + } + _ => Err(RetryError::Retry(e.into())), + }, + Err(_) => Ok(TxStatus::Dropped), + }, } - } - - Ok(TxStatus::Dropped) - } - .await; + }) + .await; (hash, result) } diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index 8bdb35bb33..adb205a87e 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,10 +1,44 @@ use crate::multi_sequence::MultiChainSequence; use eyre::Result; -use forge_script_sequence::ScriptSequence; +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 std::path::Path; +use std::{ + fmt::{Error, Write}, + path::Path, +}; + +/// Format transaction details for display +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.opcode.is_any_create() { + if let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) { + writeln!(output, "contract: {name}({addr})")?; + } + } + + // Show decoded function if available + if let (Some(func), Some(args)) = (&tx.function, &tx.arguments) { + if args.is_empty() { + writeln!(output, "data (decoded): {func}()")?; + } else { + writeln!(output, "data (decoded): {func}(")?; + for (i, arg) in args.iter().enumerate() { + writeln!(&mut output, " {}{}", arg, if i + 1 < args.len() { "," } else { "" })?; + } + writeln!(output, ")")?; + } + } + + writeln!(output)?; + Ok(output) +} /// Returns the commit hash of the project if it exists pub fn get_commit_hash(root: &Path) -> Option { @@ -57,6 +91,20 @@ impl ScriptSequenceKind { Ok(()) } + + pub fn show_transactions(&self) -> Result<()> { + for sequence in self.sequences() { + if !sequence.transactions.is_empty() { + sh_println!("\nChain {}\n", sequence.chain)?; + + for (i, tx) in sequence.transactions.iter().enumerate() { + sh_print!("{}", format_transaction(i + 1, tx)?)?; + } + } + } + + Ok(()) + } } impl Drop for ScriptSequenceKind { diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 6ad1711368..3ea337b874 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -65,7 +65,7 @@ impl VerifyBundle { verifier: VerifierArgs, ) -> Self { let num_of_optimizations = - if config.optimizer { Some(config.optimizer_runs) } else { None }; + if config.optimizer == Some(true) { config.optimizer_runs } else { None }; let config_path = config.get_config_path(); @@ -123,9 +123,14 @@ impl VerifyBundle { warn!("Skipping verification of Vyper contract: {}", artifact.name); } + // Strip artifact profile from contract name when creating contract info. let contract = ContractInfo { path: Some(artifact.source.to_string_lossy().to_string()), - name: artifact.name.clone(), + name: artifact + .name + .strip_suffix(&format!(".{}", &artifact.profile)) + .unwrap_or_else(|| &artifact.name) + .to_string(), }; // We strip the build metadadata information, since it can lead to diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 8c146aa05d..9b907eb3ad 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -165,7 +165,7 @@ edition = "2021" write!(&mut lib_contents, "{contents}")?; } else { fs::write(path, contents).wrap_err("failed to write to file")?; - writeln!(&mut lib_contents, "pub mod {name};")?; + write_mod_name(&mut lib_contents, &name)?; } } @@ -194,12 +194,7 @@ edition = "2021" let name = instance.name.to_lowercase(); if !single_file { // Module - write!( - mod_contents, - r#"pub mod {}; - "#, - instance.name.to_lowercase() - )?; + write_mod_name(&mut mod_contents, &name)?; let mut contents = String::new(); write!(contents, "{}", instance.expansion.as_ref().unwrap())?; @@ -270,12 +265,7 @@ edition = "2021" .to_string(); self.check_file_contents(&path, &tokens)?; - - write!( - &mut super_contents, - r#"pub mod {name}; - "# - )?; + write_mod_name(&mut super_contents, &name)?; } let super_path = @@ -344,3 +334,12 @@ edition = "2021" Ok(()) } } + +fn write_mod_name(contents: &mut String, name: &str) -> Result<()> { + if syn::parse_str::(&format!("pub mod {name};")).is_ok() { + write!(contents, "pub mod {name};")?; + } else { + write!(contents, "pub mod r#{name};")?; + } + Ok(()) +} diff --git a/crates/strategy/zksync/src/cheatcode/runner/mod.rs b/crates/strategy/zksync/src/cheatcode/runner/mod.rs index 3a9f4f4e44..218de0b8a8 100644 --- a/crates/strategy/zksync/src/cheatcode/runner/mod.rs +++ b/crates/strategy/zksync/src/cheatcode/runner/mod.rs @@ -564,7 +564,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategyRunner { gas_limit: input.gas_limit(), caller: input.caller(), }, - decoded_log, + &decoded_log, ); }, ); @@ -731,7 +731,7 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategyRunner { gas_limit: call.gas_limit, caller: call.caller, }, - decoded_log, + &decoded_log, ); }, ); diff --git a/crates/strategy/zksync/src/cheatcode/runner/utils.rs b/crates/strategy/zksync/src/cheatcode/runner/utils.rs index c4899e3934..c105995db4 100644 --- a/crates/strategy/zksync/src/cheatcode/runner/utils.rs +++ b/crates/strategy/zksync/src/cheatcode/runner/utils.rs @@ -98,8 +98,8 @@ pub(super) fn get_artifact_code( .or_else(|| { // If we know the current script/test contract solc version, try to // filter by it - config.running_version.as_ref().and_then(|version| { - filtered.iter().find(|(id, _)| id.version == *version) + config.running_artifact.as_ref().and_then(|artifact| { + filtered.iter().find(|(id, _)| id.version == artifact.version) }) }) .ok_or_else(|| Error::display("multiple matching artifacts found")) diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 7b839c258c..9090c7a378 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -30,7 +30,7 @@ serde_json.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } rand.workspace = true -snapbox = { version = "0.6", features = ["json", "regex"] } +snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } tokio.workspace = true tempfile.workspace = true tower-http = { version = "0.6.2", features = ["cors"] } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 7133546698..d894cb3d3d 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -207,6 +207,7 @@ impl ExtTester { 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/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index d88669d466..6888011975 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -121,7 +121,7 @@ impl VerifyBytecodeArgs { /// bytecode. pub async fn run(mut self) -> Result<()> { // Setup - let config = self.load_config_emit_warnings(); + let config = self.load_config()?; let provider = utils::get_provider(&config)?; let strategy = utils::get_executor_strategy(&config); diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index c4f0e2f9c3..34391e2ae7 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -158,7 +158,7 @@ impl VerificationProvider for EtherscanVerificationProvider { /// Executes the command to check verification status on Etherscan async fn check(&self, args: VerifyCheckArgs) -> Result<()> { - let config = args.try_load_config_emit_warnings()?; + let config = args.load_config()?; let etherscan = self.client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), @@ -226,8 +226,7 @@ impl EtherscanVerificationProvider { args: &VerifyArgs, context: &CompilerVerificationContext, ) -> Result<(Client, VerifyContract)> { - let config = args.try_load_config_emit_warnings()?; - + let config = args.load_config()?; let etherscan = self.client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), @@ -360,8 +359,10 @@ impl EtherscanVerificationProvider { if code_format == CodeFormat::SingleFile { verify_args = if let Some(optimizations) = args.num_of_optimizations { verify_args.optimized().runs(optimizations as u32) - } else if context.config().optimizer { - verify_args.optimized().runs(context.config().optimizer_runs.try_into()?) + } else if context.config().optimizer == Some(true) { + verify_args + .optimized() + .runs(context.config().optimizer_runs.unwrap_or(200).try_into()?) } else { verify_args.not_optimized() }; @@ -546,7 +547,7 @@ mod tests { root.as_os_str().to_str().unwrap(), ]); - let config = args.load_config(); + let config = args.load_config().unwrap(); let etherscan = EtherscanVerificationProvider::default(); let client = etherscan @@ -573,7 +574,7 @@ mod tests { root.as_os_str().to_str().unwrap(), ]); - let config = args.load_config(); + let config = args.load_config().unwrap(); let etherscan = EtherscanVerificationProvider::default(); let client = etherscan diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index dd1a62dcc7..27d2aae244 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -168,8 +168,8 @@ impl fmt::Display for VerificationProviderType { #[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)] pub enum VerificationProviderType { - #[default] Etherscan, + #[default] Sourcify, Blockscout, Oklink, @@ -182,6 +182,9 @@ pub enum VerificationProviderType { impl VerificationProviderType { /// Returns the corresponding `VerificationProvider` for the key pub fn client(&self, key: &Option) -> Result> { + if key.as_ref().is_some_and(|k| !k.is_empty()) && matches!(self, Self::Sourcify) { + return Ok(Box::::default()); + } match self { Self::Etherscan => { if key.as_ref().is_none_or(|key| key.is_empty()) { @@ -189,7 +192,12 @@ impl VerificationProviderType { } Ok(Box::::default()) } - Self::Sourcify => Ok(Box::::default()), + 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 any other provider" + )?; + Ok(Box::::default()) + } Self::Blockscout => Ok(Box::::default()), Self::Oklink => Ok(Box::::default()), Self::ZKsync => Ok(Box::::default()), diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 403f5be61a..de5567acfe 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -239,18 +239,22 @@ fn find_mismatch_in_settings( ); mismatches.push(str); } - let local_optimizer: u64 = if local_settings.optimizer { 1 } else { 0 }; + let local_optimizer: u64 = if local_settings.optimizer == Some(true) { 1 } else { 0 }; if etherscan_settings.optimization_used != local_optimizer { let str = format!( "Optimizer mismatch: local={}, onchain={}", - local_settings.optimizer, etherscan_settings.optimization_used + local_settings.optimizer.unwrap_or(false), + etherscan_settings.optimization_used ); mismatches.push(str); } - if etherscan_settings.runs != local_settings.optimizer_runs as u64 { + 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, etherscan_settings.runs + local_settings.optimizer_runs.unwrap(), + etherscan_settings.runs ); mismatches.push(str); } @@ -311,7 +315,7 @@ pub fn check_args_len( args: &Bytes, ) -> Result<(), eyre::ErrReport> { if let Some(constructor) = artifact.abi.as_ref().and_then(|abi| abi.constructor()) { - if !constructor.inputs.is_empty() && args.len() == 0 { + if !constructor.inputs.is_empty() && args.is_empty() { eyre::bail!( "Contract expects {} constructor argument(s), but none were provided", constructor.inputs.len() diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 932ec8b3fa..4850ba9ab1 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -28,7 +28,7 @@ use std::path::PathBuf; #[derive(Clone, Debug, Parser)] pub struct VerifierArgs { /// The contract verification provider to use. - #[arg(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] + #[arg(long, help_heading = "Verifier options", default_value = "sourcify", value_enum)] pub verifier: VerificationProviderType, /// The verifier API KEY, if using a custom provider. @@ -43,7 +43,7 @@ pub struct VerifierArgs { impl Default for VerifierArgs { fn default() -> Self { Self { - verifier: VerificationProviderType::Etherscan, + verifier: VerificationProviderType::Sourcify, verifier_api_key: None, verifier_url: None, } @@ -190,7 +190,7 @@ impl figment::Provider for VerifyArgs { impl VerifyArgs { /// Run the verify command to submit the contract's source code for verification on etherscan pub async fn run(mut self) -> Result<()> { - let config = self.load_config_emit_warnings(); + let config = self.load_config()?; if self.guess_constructor_args && config.get_rpc_url().is_none() { eyre::bail!( @@ -269,7 +269,7 @@ impl VerifyArgs { /// Resolves [VerificationContext] object either from entered contract name or by trying to /// match bytecode located at given address. pub async fn resolve_context(&self) -> Result { - let mut config = self.load_config_emit_warnings(); + let mut config = self.load_config()?; config.libraries.extend(self.libraries.clone()); let project = config.project()?; @@ -405,7 +405,7 @@ impl VerifyArgs { } pub async fn zk_resolve_context(&self) -> Result { - let mut config = self.load_config_emit_warnings(); + let mut config = self.load_config()?; config.libraries.extend(self.libraries.clone()); let project = foundry_config::zksync::config_create_project(&config, config.cache, false)?; diff --git a/crates/zksync/compilers/Cargo.toml b/crates/zksync/compilers/Cargo.toml index 9f538b587f..f4dfc50d1a 100644 --- a/crates/zksync/compilers/Cargo.toml +++ b/crates/zksync/compilers/Cargo.toml @@ -13,7 +13,7 @@ exclude.workspace = true [dependencies] foundry-compilers = { workspace = true, features = ["svm-solc"] } -foundry-compilers-artifacts-solc = "0.12.7" +foundry-compilers-artifacts-solc = "0.13.0" alloy-primitives.workspace = true alloy-json-abi.workspace = true tracing.workspace = true diff --git a/crates/zksync/compilers/src/artifacts/mod.rs b/crates/zksync/compilers/src/artifacts/mod.rs index 104a70c5cc..1303c0506d 100644 --- a/crates/zksync/compilers/src/artifacts/mod.rs +++ b/crates/zksync/compilers/src/artifacts/mod.rs @@ -1,8 +1,8 @@ //! zksolc artifacts to be used in `foundry-compilers` -use foundry_compilers_artifacts_solc::{ + +use foundry_compilers::artifacts::{ Bytecode, BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, }; - use semver::Version; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; diff --git a/crates/zksync/compilers/src/compilers/zksolc/input.rs b/crates/zksync/compilers/src/compilers/zksolc/input.rs index aa25d9e694..650229cdd7 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/input.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/input.rs @@ -2,10 +2,11 @@ use super::{settings::ZkSolcSettings, ZkSettings}; use era_solc::standard_json::input::settings::{error_type::ErrorType, warning_type::WarningType}; use foundry_compilers::{ + artifacts::{Remapping, Source, Sources}, compilers::{solc::SolcLanguage, CompilerInput}, solc, }; -use foundry_compilers_artifacts_solc::{remappings::Remapping, serde_helpers, Source, Sources}; +use foundry_compilers_artifacts_solc::serde_helpers::tuple_vec_map; use semver::Version; use serde::{Deserialize, Serialize}; use std::{ @@ -178,7 +179,7 @@ pub struct StandardJsonCompilerInput { /// compiler language pub language: SolcLanguage, /// sources to compile - #[serde(with = "serde_helpers::tuple_vec_map")] + #[serde(with = "tuple_vec_map")] pub sources: Vec<(PathBuf, Source)>, /// compiler settings pub settings: ZkSettings, diff --git a/crates/zksync/compilers/src/compilers/zksolc/settings.rs b/crates/zksync/compilers/src/compilers/zksolc/settings.rs index 12faf13e8c..8be44fd97e 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/settings.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/settings.rs @@ -2,12 +2,13 @@ use crate::artifacts::output_selection::OutputSelection as ZkOutputSelection; use era_solc::standard_json::input::settings::{error_type::ErrorType, warning_type::WarningType}; use foundry_compilers::{ - artifacts::{serde_helpers, EvmVersion, Libraries}, + artifacts::{ + output_selection::OutputSelection, serde_helpers, EvmVersion, Libraries, Remapping, + }, compilers::CompilerSettings, error::Result, solc, CompilerSettingsRestrictions, }; -use foundry_compilers_artifacts_solc::{output_selection::OutputSelection, remappings::Remapping}; use semver::Version; use serde::{Deserialize, Serialize}; use std::{ diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index aee397ee06..1d78dbd5a2 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -54,9 +54,10 @@ use crate::{ }, }, }; -use foundry_evm_abi::{ - patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS, -}; + +use foundry_evm_abi::console::{self, ds::Console}; + +use super::HARDHAT_CONSOLE_ADDRESS; /// Represents the result of execution a [`L2Tx`] on EraVM #[derive(Debug)] @@ -742,13 +743,9 @@ impl ConsoleLogParser { return; } - let mut input = current_call.input.clone(); - - // Patch the Hardhat-style selector (`uint` instead of `uint256`) - patch_hh_console_selector(&mut input); + let input = current_call.input.clone(); - // Decode the call - let Ok(call) = HardhatConsole::HardhatConsoleCalls::abi_decode(&input, false) else { + let Ok(call) = console::hh::ConsoleCalls::abi_decode(&input, false) else { return; }; diff --git a/crates/zksync/core/src/vm/mod.rs b/crates/zksync/core/src/vm/mod.rs index 16b1f07a85..a7593ff2fd 100644 --- a/crates/zksync/core/src/vm/mod.rs +++ b/crates/zksync/core/src/vm/mod.rs @@ -6,6 +6,7 @@ mod runner; mod storage_view; mod tracers; +use alloy_primitives::{address, Address}; pub use env::ZkEnv; pub use farcall::{SELECTOR_CONTRACT_DEPLOYER_CREATE, SELECTOR_CONTRACT_DEPLOYER_CREATE2}; pub use inspect::{ @@ -16,3 +17,8 @@ pub use runner::{ ZkCreateInputs, }; pub use tracers::cheatcode::CheatcodeTracerContext; + +/// The Hardhat console address. +/// +/// See: +pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); diff --git a/crates/zksync/core/src/vm/tracers/cheatcode.rs b/crates/zksync/core/src/vm/tracers/cheatcode.rs index 1a45836018..06bac82089 100644 --- a/crates/zksync/core/src/vm/tracers/cheatcode.rs +++ b/crates/zksync/core/src/vm/tracers/cheatcode.rs @@ -31,7 +31,7 @@ use crate::{ hash_bytecode, vm::{ farcall::{CallAction, CallDepth, FarCallHandler}, - ZkEnv, + ZkEnv, HARDHAT_CONSOLE_ADDRESS, }, ZkPaymasterData, EMPTY_CODE, }; @@ -173,11 +173,8 @@ impl CheatcodeTracer { value: rU256, ) -> bool { // The following addresses are expected to have empty bytecode - let ignored_known_addresses = [ - foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS, - self.call_context.tx_caller, - self.call_context.msg_sender, - ]; + let ignored_known_addresses = + [HARDHAT_CONSOLE_ADDRESS, self.call_context.tx_caller, self.call_context.msg_sender]; // Skip empty code check for empty calldata with non-zero value (Transfers) if calldata.is_empty() && !value.is_zero() { diff --git a/docs/dev/README.md b/docs/dev/README.md index db7cc3f537..cde2a01c31 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -1,42 +1,82 @@ -# Contributing Quick Start +# Developer Docs -The foundry Rust project is organized as a regular [Cargo workspace][cargo-workspace]. +The Foundry project is organized as a regular [Cargo workspace][cargo-workspace]. -Simply running +## Installation requirements +- [Rust](https://rustup.rs/) +- Make + +We use `cargo-nextest` as test runner (both locally and in the [CI](#ci)): + +- [Nextest](https://nexte.st/docs/installation/pre-built-binaries/#with-cargo-binstall) + +## Recommended + +If you are working in VSCode, we recommend you install the [rust-analyzer](https://rust-analyzer.github.io/) extension, and use the following VSCode user settings: + +```json +"editor.formatOnSave": true, +"rust-analyzer.rustfmt.extraArgs": ["+nightly"], +"[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" +} +``` + +Note that we use Rust's latest `nightly` for formatting. If you see `;` being inserted by your code editor it is a good indication you are on `stable`. + +## Getting started + +Build the project. + +```sh +$ make build +``` + +Run all tests. + +```sh +$ make test ``` -$ cargo test + +Run all tests and linters in preparation for a PR. + +```sh +$ make pr ``` -should be enough to get you started! +## Contents + +- [Architecture](./architecture.md) +- [Cheatcodes](./cheatcodes.md) +- [Debugging](./debugging.md) +- [Scripting](./scripting.md) -To learn more about how foundry's tools works, see [./architecture.md](./architecture.md). -It also explains the high-level layout of some aspects of the source code. -To read more about how to use it, see [📖 Foundry Book][foundry-book] -Note though, that the internal documentation is very incomplete. +_Note: This is incomplete and possibly outdated_ -# Getting in Touch +## Getting in Touch See also [Getting Help](../../README.md#getting-help) -# Issue Labels +## Issue Labels + +Whenever a ticket is initially opened a [`T-needs-triage`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-needs-triage) label is assigned. This means that a member has yet to correctly label it. + +If this is your first time contributing have a look at our [`first-issue`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3A%22first+issue%22) tickets. These are tickets we think are a good way to get familiar with the codebase. + +We classify the tickets in two major categories: [`T-feature`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-feature) and [`T-bug`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-bug). Additional labels are usually applied to help categorize the ticket for future reference. + +We also make use of [`T-meta`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-meta) aggregation tickets. These tickets are tickets to collect related features and bugs. + +We also have [`T-discuss`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-to-discuss) tickets that require further discussion before proceeding on an implementation. Feel free to jump into the conversation! + +## CI -- [good-first-issue](https://github.com/foundry-rs/foundry/labels/good%20first%20issue) - are good issues to get into the project. -- [D-easy](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-easy), - [D-average](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-medium), - [D-hard](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-hard), - [D-chore](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-chore), - labels indicate how hard it would be to write a fix or add a feature. +We use GitHub Actions for continuous integration (CI). -# CI +We use [cargo-nextest][nextest] as the test runner. -We use GitHub Actions for CI. -We use [cargo-nextest][nextest] as the test runner -If `cargo test` passes locally, that's a good sign that CI will be green as well. -We also have tests that make use of forking mode which can be long running if the required state is not already cached locally. -Forking-related tests are executed exclusively in a separate CI job, they are identified by `fork` in their name. -So all of them can be easily skipped by `cargo t -- --skip fork` +If `make test` passes locally, that's a good sign that CI will be green as well. [foundry-book]: https://book.getfoundry.sh [cargo-workspace]: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 32b922d49d..d0b9640605 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -6,6 +6,7 @@ This document describes the high-level architecture of Foundry. Foundry's EVM tooling. This is built around [`revm`](https://github.com/bluealloy/revm) and has additional implementation of: + - [cheatcodes](./cheatcodes.md) a set of solidity calls dedicated to testing which can manipulate the environment in which the execution is run ### `config/` diff --git a/docs/dev/cheatcodes.md b/docs/dev/cheatcodes.md index 1e95bf7f26..0815ca66be 100644 --- a/docs/dev/cheatcodes.md +++ b/docs/dev/cheatcodes.md @@ -18,6 +18,7 @@ current state of the EVM. ## [Foundry inspectors](../../crates/evm/evm/src/inspectors/) The [`evm`](../../crates/evm/evm/) crate has a variety of inspectors for different use cases, such as + - coverage - tracing - debugger @@ -117,6 +118,7 @@ The `Cheatcode` derive macro also parses the `#[cheatcode(...)]` attributes on f used to specify additional properties of the JSON interface. These are all the attributes that can be specified on cheatcode functions: + - `#[cheatcode(group = )]`: The group that the cheatcode belongs to. Required. - `#[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. @@ -127,6 +129,7 @@ Multiple attributes can be specified by separating them with commas, e.g. `#[che This trait defines the interface that all cheatcode implementations must implement. There are two 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 diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md index df8664440d..f72b8f4bfb 100644 --- a/docs/dev/debugging.md +++ b/docs/dev/debugging.md @@ -1,8 +1,8 @@ -## Debugging Foundry tools +# Debugging This is a working document intended to outline some commands contributors can use to debug various parts of Foundry. -### Logs +## Logs All crates use [tracing](https://docs.rs/tracing/latest/tracing/) for logging. A console formatter is installed in each binary (`cast`, `forge`, `anvil`). @@ -10,15 +10,12 @@ By setting `RUST_LOG=` you can get a lot more info out of Forge and Cast The most basic valid filter is a log level, of which these are valid: -- `error` -- `warn` -- `info` -- `debug` -- `trace` +- `error` +- `warn` +- `info` +- `debug` +- `trace` Filters are explained in detail in the [`env_logger` crate docs](https://docs.rs/env_logger). -### Compiler input and output - -You can get the compiler input JSON and output JSON by passing the `--build-info` flag. -This will create two files: one for the input and one for the output. +You can also use the `dbg!` macro from Rust's standard library. diff --git a/docs/dev/scripting.md b/docs/dev/scripting.md index 8e5fc03ca1..cdface73a2 100644 --- a/docs/dev/scripting.md +++ b/docs/dev/scripting.md @@ -1,10 +1,10 @@ +# Scripting -# Scripting - Flow Diagrams - -1. [High level overview](#high-level-overview) - 1. [Notes](#notes) -2. [Script Execution](#script-execution) -3. [Nonce Management](#nonce-management) +- [Scripting](#scripting) + - [High level overview](#high-level-overview) + - [Notes](#notes) + - [Script Execution](#script-execution) + - [Nonce Management](#nonce-management) ## High level overview @@ -48,19 +48,20 @@ graph TD; ``` ### Notes -1) `[..]` - concurrently executed -2) The bit below does not actually influence the state initially defined by `--broadcast`. It only happens because there might be private keys declared inside the script that need to be collected again. `--resume` only resumes **publishing** the transactions, nothing more! +1. `[..]` - concurrently executed + +2. The bit below does not actually influence the state initially defined by `--broadcast`. It only happens because there might be private keys declared inside the script that need to be collected again. `--resume` only resumes **publishing** the transactions, nothing more! ```mermaid graph TD; ScriptArgs::execute-- "(resume || verify) && !broadcast" -->ScriptArgs::resume_deployment; ``` -3) `ScriptArgs::execute` executes the script, while `ScriptArgs::onchain_simulation` only executes the broadcastable transactions collected by `ScriptArgs::execute`. - +3. `ScriptArgs::execute` executes the script, while `ScriptArgs::onchain_simulation` only executes the broadcastable transactions collected by `ScriptArgs::execute`. ## Script Execution + ```mermaid graph TD; subgraph ScriptArgs::execute @@ -85,7 +86,6 @@ Executor::call-. BroadcastableTransactions .->ScriptArgs::handle_broadcastable_t ``` - ## Nonce Management During the first execution stage on `forge script`, foundry has to adjust the nonce from the sender to make sure the execution and state are as close as possible to its on-chain representation. @@ -94,7 +94,6 @@ Making sure that `msg.sender` is our signer when calling `setUp()` and `run()` a We skip this, if the user hasn't set a sender and they're using the `Config::DEFAULT_SENDER`. - ```mermaid graph TD @@ -135,4 +134,4 @@ graph TD L-->M[cheatcode.corrected_nonce=true]; M-->continue_run; continue_run-->end_run; -``` \ No newline at end of file +``` diff --git a/foundryup-zksync/foundryup-zksync b/foundryup-zksync/foundryup-zksync index d926797402..2edb1f9ce9 100755 --- a/foundryup-zksync/foundryup-zksync +++ b/foundryup-zksync/foundryup-zksync @@ -1,11 +1,18 @@ #!/usr/bin/env bash 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="0.3.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_PATH="$FOUNDRY_BIN_DIR/foundryup" FOUNDRYUP_JOBS="" @@ -21,6 +28,8 @@ main() { case $1 in --) shift; break;; + -v|--version) shift; version;; + -U|--update) shift; update;; -r|--repo) shift; FOUNDRYUP_REPO=$1;; -b|--branch) shift; FOUNDRYUP_BRANCH=$1;; -i|--install) shift; FOUNDRYUP_VERSION=$1;; @@ -60,13 +69,15 @@ main() { fi fi + check_bins_in_use + # Installs foundry from a local repository if --path parameter is provided if [[ -n "$FOUNDRYUP_LOCAL_REPO" ]]; then need_cmd cargo # Ignore branches/versions as we do not want to modify local git state if [ -n "$FOUNDRYUP_REPO" ] || [ -n "$FOUNDRYUP_BRANCH" ] || [ -n "$FOUNDRYUP_VERSION" ]; then - warn "--branch, --version, and --repo arguments are ignored during local install" + warn "--branch, --install, --use, and --repo arguments are ignored during local install" fi # Enter local repo and build @@ -270,10 +281,10 @@ EOF need_cmd cargo FOUNDRYUP_BRANCH=${FOUNDRYUP_BRANCH:-main} REPO_PATH="$FOUNDRY_DIR/$FOUNDRYUP_REPO" + AUTHOR="$(echo "$FOUNDRYUP_REPO" | cut -d'/' -f1 -)" # If repo path does not exist, grab the author from the repo, make a directory in .foundry, cd to it and clone. if [ ! -d "$REPO_PATH" ]; then - AUTHOR="$(echo "$FOUNDRYUP_REPO" | cut -d'/' -f1 -)" ensure mkdir -p "$FOUNDRY_DIR/$AUTHOR" cd "$FOUNDRY_DIR/$AUTHOR" ensure git clone "https://github.com/$FOUNDRYUP_REPO" @@ -284,23 +295,39 @@ EOF ensure git fetch origin "${FOUNDRYUP_BRANCH}:remotes/origin/${FOUNDRYUP_BRANCH}" ensure git checkout "origin/${FOUNDRYUP_BRANCH}" - # If set, checkout specific commit from branch + # Create custom version based on the install method, e.g.: + # - foundry-rs-commit-c22c4cc96b0535cd989ee94b79da1b19d236b8db + # - foundry-rs-pr-1 + # - foundry-rs-branch-chore-bump-forge-std if [ -n "$FOUNDRYUP_COMMIT" ]; then - say "installing at commit $FOUNDRYUP_COMMIT" + # If set, checkout specific commit from branch ensure git checkout "$FOUNDRYUP_COMMIT" + FOUNDRYUP_VERSION=$AUTHOR-commit-$FOUNDRYUP_COMMIT + elif [ -n "$FOUNDRYUP_PR" ]; then + FOUNDRYUP_VERSION=$AUTHOR-pr-$FOUNDRYUP_PR + else + if [ -n "$FOUNDRYUP_BRANCH" ]; then + NORMALIZED_BRANCH="$(echo "$FOUNDRYUP_BRANCH" | tr / -)" + FOUNDRYUP_VERSION=$AUTHOR-branch-$NORMALIZED_BRANCH + fi fi + say "installing version $FOUNDRYUP_VERSION" - # Build the repo and install the binaries locally to the .foundry bin directory. + # Build the repo. ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}" + # Create foundry custom version directory. + ensure mkdir -p $FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION for bin in "${BINS[@]}"; do for try_path in target/release/$bin target/release/$bin.exe; do if [ -f "$try_path" ]; then - [ -e "$FOUNDRY_BIN_DIR/$bin" ] && warn "overwriting existing $bin in $FOUNDRY_BIN_DIR" - mv -f "$try_path" "$FOUNDRY_BIN_DIR" + mv -f "$try_path" "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" fi done done + # Use newly built version. + use + # If help2man is installed, use it to add Foundry man pages. if check_cmd help2man; then for bin in "${BINS[@]}"; do @@ -325,6 +352,8 @@ USAGE: 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 -l, --list List versions installed from built binaries -u, --use Use a specific installed version from built binaries @@ -339,20 +368,40 @@ OPTIONS: EOF } +version() { + say "$FOUNDRYUP_INSTALLER_VERSION" + exit 0 +} + +update() { + say "updating foundryup..." + + # Download to a temporary file first + tmp_file="$(mktemp)" + ensure download "$FOUNDRY_BIN_URL" "$tmp_file" + + # Replace the current foundryup with the downloaded file + ensure mv "$tmp_file" "$FOUNDRY_BIN_PATH" + ensure chmod +x "$FOUNDRY_BIN_PATH" + + say "successfully updated foundryup" + exit 0 +} + list() { if [ -d "$FOUNDRY_VERSIONS_DIR" ]; then for VERSION in $FOUNDRY_VERSIONS_DIR/*; do say "${VERSION##*/}" for bin in "${BINS[@]}"; do bin_path="$VERSION/$bin" - say "- $(ensure "$bin_path" --version)" + say "- $(ensure "$bin_path" -V)" done printf "\n" done else for bin in "${BINS[@]}"; do bin_path="$FOUNDRY_BIN_DIR/$bin" - say "- $(ensure "$bin_path" --version)" + say "- $(ensure "$bin_path" -V)" done fi exit 0 @@ -362,11 +411,28 @@ use() { [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" if [ -d "$FOUNDRY_VERSION_DIR" ]; then + + check_bins_in_use + for bin in "${BINS[@]}"; do bin_path="$FOUNDRY_BIN_DIR/$bin" cp $FOUNDRY_VERSION_DIR/$bin $bin_path # Print usage msg - say "use - $(ensure "$bin_path" --version)" + say "use - $(ensure "$bin_path" -V)" + + # Check if the default path of the binary is not in FOUNDRY_BIN_DIR + which_path="$(command -v "$bin" || true)" + if [ -n "$which_path" ] && [ "$which_path" != "$bin_path" ]; then + warn "" + cat 1>&2 </dev/null } +check_bins_in_use() { + if check_cmd pgrep; then + for bin in "${BINS[@]}"; do + if pgrep -x "$bin" >/dev/null; then + err "Error: '$bin' is currently running. Please stop the process and try again." + fi + done + else + warn "Make sure no foundry process is running during the install process!" + fi +} + # Run a command that should never fail. If the command fails execution # will immediately terminate with an error showing the failing command. ensure() { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index f120a006f3..891a592c17 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -24,6 +24,7 @@ interface Vm { struct DebugStep { uint256[] stack; bytes memoryInput; uint8 opcode; uint64 depth; bool isOutOfGas; address contractAddr; } struct BroadcastTxSummary { bytes32 txHash; BroadcastTxType txType; address contractAddress; uint64 blockNumber; bool success; } struct SignedDelegation { uint8 v; bytes32 r; bytes32 s; uint64 nonce; address implementation; } + struct PotentialRevert { address reverter; bool partialMatch; bytes revertData; } function _expectCheatcodeRevert() external; function _expectCheatcodeRevert(bytes4 revertData) external; function _expectCheatcodeRevert(bytes calldata revertData) external; @@ -149,6 +150,8 @@ interface Vm { function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; function assumeNoRevert() external pure; + function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; + function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; function attachDelegation(SignedDelegation calldata signedDelegation) external; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; diff --git a/testdata/default/cheats/AssumeNoRevert.t.sol b/testdata/default/cheats/AssumeNoRevert.t.sol new file mode 100644 index 0000000000..ea6d2d9747 --- /dev/null +++ b/testdata/default/cheats/AssumeNoRevert.t.sol @@ -0,0 +1,153 @@ +// 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"; + +contract ReverterB { + /// @notice has same error selectors as contract below to test the `reverter` param + error MyRevert(); + error SpecialRevertWithData(uint256 x); + + function revertIf2(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithData() public pure returns (bool) { + revert SpecialRevertWithData(2); + } +} + +contract Reverter { + error MyRevert(); + error RevertWithData(uint256 x); + error UnusedError(); + + ReverterB public immutable subReverter; + + constructor() { + subReverter = new ReverterB(); + } + + function myFunction() public pure returns (bool) { + revert MyRevert(); + } + + function revertIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithDataIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert RevertWithData(2); + } + return true; + } + + function twoPossibleReverts(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } else if (x == 3) { + revert RevertWithData(3); + } + return true; + } +} + +contract ReverterTest is Test { + Reverter reverter; + Vm _vm = Vm(HEVM_ADDRESS); + + function setUp() public { + reverter = new Reverter(); + } + + /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector + function testAssumeSelector(uint256 x) public view { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector and data + function testAssumeWithDataSingle(uint256 x) public view { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 2), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertWithDataIf2(x); + } + + /// @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.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), + partialMatch: true, + reverter: address(0) + }) + ); + reverter.revertWithDataIf2(x); + } + + /// @dev Test that `assumeNoRevert` assumptions are not cleared after a cheatcode call + function testAssumeNotClearedAfterCheatcodeCall(uint256 x) public { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + _vm.warp(block.timestamp + 1000); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects two different error selectors + function testMultipleAssumesPasses(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(reverter) + }); + revertData[1] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 3), + partialMatch: false, + reverter: address(reverter) + }); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } + + /// @dev Test that `assumeNoRevert` correctly interacts with itself when partially matching on the error selector + function testMultipleAssumes_Partial(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), + partialMatch: true, + reverter: address(reverter) + }); + revertData[1] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(reverter) + }); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } +} diff --git a/testdata/default/cheats/AttachDelegation.t.sol b/testdata/default/cheats/AttachDelegation.t.sol index 7befc9a320..2b2e829ae3 100644 --- a/testdata/default/cheats/AttachDelegation.t.sol +++ b/testdata/default/cheats/AttachDelegation.t.sol @@ -86,6 +86,7 @@ contract AttachDelegationTest is DSTest { assertEq(token.balanceOf(bob), 200); } + /// forge-config: default.allow_internal_expect_revert = true function testAttachDelegationRevertInvalidSignature() public { Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); // change v from 1 to 0 @@ -109,7 +110,7 @@ contract AttachDelegationTest is DSTest { // send tx to increment alice's nonce token.mint(1, bob); - vm.expectRevert("vm.attachDelegation: invalid nonce"); + vm._expectCheatcodeRevert("vm.attachDelegation: invalid nonce"); vm.attachDelegation(signedDelegation); } diff --git a/testdata/default/cheats/BroadcastRawTransaction.t.sol b/testdata/default/cheats/BroadcastRawTransaction.t.sol index 5bd400a9f7..36682bc893 100644 --- a/testdata/default/cheats/BroadcastRawTransaction.t.sol +++ b/testdata/default/cheats/BroadcastRawTransaction.t.sol @@ -117,8 +117,8 @@ contract BroadcastRawTransactionTest is DSTest { assertEq(address(from).balance, balance - (gasPrice * 21_000) - amountSent); assertEq(address(to).balance, amountSent); - vm.expectRevert(); - assert(3 == 4); + vm._expectCheatcodeRevert(); + vm.assertFalse(true); } function test_execute_multiple_signed_tx() public { diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol index 0ec83a72d5..0029ec6cd8 100644 --- a/testdata/default/cheats/GetFoundryVersion.t.sol +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -11,17 +11,35 @@ contract GetFoundryVersionTest is DSTest { vm.zkVm(false); string memory fullVersionString = vm.getFoundryVersion(); - string[] memory versionComponents = vm.split(fullVersionString, "+"); - require(versionComponents.length == 3, "Invalid version format"); + // Step 1: Split the version at "+" + string[] memory plusSplit = vm.split(fullVersionString, "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); - string memory semanticVersion = versionComponents[0]; + // Step 2: Extract parts + string memory semanticVersion = plusSplit[0]; // "0.3.0-dev" + string memory metadata = plusSplit[1]; // "34389e7850.1737037814.debug" + + // Step 3: Further split metadata by "." + string[] memory metadataComponents = vm.split(metadata, "."); + require(metadataComponents.length == 3, "Invalid version format: Metadata should have 3 components"); + + // Step 4: Extract values + string memory commitHash = metadataComponents[0]; // "34389e7850" + string memory timestamp = metadataComponents[1]; // "1737037814" + string memory buildType = metadataComponents[2]; // "debug" + + // Validate semantic version (e.g., "0.3.0-stable" or "0.3.0-nightly") require(bytes(semanticVersion).length > 0, "Semantic version is empty"); - string memory commitHash = versionComponents[1]; - require(bytes(commitHash).length > 0, "Commit hash is empty"); + // Validate commit hash (should be exactly 10 characters) + require(bytes(commitHash).length == 10, "Invalid commit hash length"); - uint256 buildUnixTimestamp = vm.parseUint(versionComponents[2]); - uint256 minimumAcceptableTimestamp = 202406111234; + // Validate UNIX timestamp (numeric) + uint256 buildUnixTimestamp = vm.parseUint(timestamp); + uint256 minimumAcceptableTimestamp = 1700000000; // Adjust as needed require(buildUnixTimestamp >= minimumAcceptableTimestamp, "Build timestamp is too old"); + + // Validate build profile (e.g., "debug" or "release") + require(bytes(buildType).length > 0, "Build type is empty"); } } diff --git a/testdata/default/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol index a5c0a5a4ff..2093c20fd5 100644 --- a/testdata/default/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -413,6 +413,7 @@ contract MemSafetyTest is DSTest { /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `MLOAD` opcode. + /// forge-config: default.allow_internal_expect_revert = true function testExpectSafeMemory_MLOAD_REVERT() public { vm.expectSafeMemory(0x80, 0x100); @@ -504,6 +505,7 @@ contract MemSafetyTest is DSTest { /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `LOG0` opcode. + /// forge-config: default.allow_internal_expect_revert = true function testExpectSafeMemory_LOG0_REVERT() public { vm.expectSafeMemory(0x80, 0x100); vm.expectRevert(); diff --git a/testdata/default/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol index f85e9c8239..f11fd20984 100644 --- a/testdata/default/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -201,8 +201,11 @@ contract MockCallRevertTest is DSTest { // post-mock assertEq(target.numberA(), 1); - vm.expectRevert(); - target.numberB(); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockRevertWithCustomError() public { @@ -216,8 +219,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(target), abi.encodeWithSelector(target.numberB.selector), customError); assertEq(target.numberA(), 1); - vm.expectRevert(customError); - target.numberB(); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(customError)); + } } function testMockNestedRevert() public { @@ -228,8 +234,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(inner), abi.encodeWithSelector(inner.numberB.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - target.sum(); + try target.sum() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCalldataRevert() public { @@ -241,8 +250,11 @@ contract MockCallRevertTest is DSTest { assertEq(target.add(6, 4), 10); - vm.expectRevert(ERROR_MESSAGE); - target.add(5, 5); + try target.add(5, 5) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testClearMockRevertedCalls() public { @@ -263,8 +275,11 @@ contract MockCallRevertTest is DSTest { assertEq(mock.add(1, 2), 3); - vm.expectRevert(ERROR_MESSAGE); - mock.add(2, 3); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallRevertWithValue() public { @@ -275,8 +290,11 @@ contract MockCallRevertTest is DSTest { assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - vm.expectRevert(ERROR_MESSAGE); - mock.pay{value: 10}(1); + try mock.pay{value: 10}(1) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallResetsMockCallRevert() public { @@ -296,8 +314,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - mock.add(2, 3); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallRevertWithCall() public { @@ -317,7 +338,10 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - mock.add(1, 2); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } } diff --git a/testdata/default/cheats/RandomCheatcodes.t.sol b/testdata/default/cheats/RandomCheatcodes.t.sol index beeee9862b..4c3e1fffdf 100644 --- a/testdata/default/cheats/RandomCheatcodes.t.sol +++ b/testdata/default/cheats/RandomCheatcodes.t.sol @@ -11,7 +11,7 @@ contract RandomCheatcodesTest is DSTest { int128 constant max = 170141183460469231731687303715884105727; function test_int128() public { - vm.expectRevert("vm.randomInt: number of bits cannot exceed 256"); + vm._expectCheatcodeRevert("vm.randomInt: number of bits cannot exceed 256"); int256 val = vm.randomInt(type(uint256).max); val = vm.randomInt(128); @@ -31,7 +31,7 @@ contract RandomCheatcodesTest is DSTest { } function test_randomUintLimit() public { - vm.expectRevert("vm.randomUint: number of bits cannot exceed 256"); + vm._expectCheatcodeRevert("vm.randomUint: number of bits cannot exceed 256"); uint256 val = vm.randomUint(type(uint256).max); } @@ -67,7 +67,7 @@ contract RandomBytesTest is DSTest { } function test_symbolic_bytes_revert() public { - vm.expectRevert(); + vm._expectCheatcodeRevert(); bytes memory val = vm.randomBytes(type(uint256).max); } diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index 8de7bcdc5b..63100f51f1 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -343,14 +343,12 @@ contract RecordAccountAccessesTest is DSTest { // contract calls to self in constructor SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); - assertEq( - "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n\n", - cheats.getStateDiff() - ); - assertEq( - "{\"0x000000000000000000000000000000000000162e\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0xde0b6b3a7640000\"},\"stateDiff\":{}},\"0x1d1499e622d69689cdf9004d05ec547d650ff211\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x1bc16d674ec80000\"},\"stateDiff\":{}}}", - cheats.getStateDiffJson() - ); + string memory callerAddress = cheats.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()); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 6); diff --git a/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol b/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol deleted file mode 100644 index aea46f4185..0000000000 --- a/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "ds-test/test.sol"; - -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; - } -} - -contract InvariantSelectorsWeightTest is DSTest { - HandlerOne handlerOne; - HandlerTwo handlerTwo; - - function setUp() public { - handlerOne = new HandlerOne(); - handlerTwo = new HandlerTwo(); - } - - function afterInvariant() public { - // selector hits uniformly distributed, see https://github.com/foundry-rs/foundry/issues/2986 - assertEq(handlerOne.hit1(), 2); - assertEq(handlerTwo.hit2(), 2); - assertEq(handlerTwo.hit3(), 3); - assertEq(handlerTwo.hit4(), 1); - assertEq(handlerTwo.hit5(), 2); - } - - function invariant_selectors_weight() public view {} -} diff --git a/testdata/default/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol index cc2f2b7859..a6226bbd66 100644 --- a/testdata/default/logs/HardhatLogs.t.sol +++ b/testdata/default/logs/HardhatLogs.t.sol @@ -25,10 +25,10 @@ contract HardhatLogsTest { } function testInts() public view { - console.log(0); - console.log(1); - console.log(2); - console.log(3); + console.log(uint256(0)); + console.log(uint256(1)); + console.log(uint256(2)); + console.log(uint256(3)); } function testStrings() public view { @@ -37,7 +37,7 @@ contract HardhatLogsTest { function testMisc() public view { console.log("testMisc", address(1)); - console.log("testMisc", 42); + console.log("testMisc", uint256(42)); } function testConsoleLog() public view { diff --git a/testdata/default/logs/console.sol b/testdata/default/logs/console.sol index feed58fb3b..4fdb6679ed 100644 --- a/testdata/default/logs/console.sol +++ b/testdata/default/logs/console.sol @@ -1,1531 +1,1560 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity 0.8; +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.22 <0.9.0; library console { - address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); + address constant CONSOLE_ADDRESS = + 0x000000000000000000636F6e736F6c652e6c6f67; - function _sendLogPayload(bytes memory payload) private view { - uint256 payloadLength = payload.length; + function _sendLogPayloadImplementation(bytes memory payload) internal view { address consoleAddress = CONSOLE_ADDRESS; + /// @solidity memory-safe-assembly assembly { - let payloadStart := add(payload, 32) - let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) + pop( + staticcall( + gas(), + consoleAddress, + add(payload, 32), + mload(payload), + 0, + 0 + ) + ) } } - function log() internal view { + function _castToPure( + function(bytes memory) internal view fnIn + ) internal pure returns (function(bytes memory) pure fnOut) { + assembly { + fnOut := fnIn + } + } + + function _sendLogPayload(bytes memory payload) internal pure { + _castToPure(_sendLogPayloadImplementation)(payload); + } + + function log() internal pure { _sendLogPayload(abi.encodeWithSignature("log()")); } - function logInt(int256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(int)", p0)); + function logInt(int256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } - function logUint(uint256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + function logUint(uint256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } - function logString(string memory p0) internal view { + function logString(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } - function logBool(bool p0) internal view { + function logBool(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } - function logAddress(address p0) internal view { + function logAddress(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } - function logBytes(bytes memory p0) internal view { + function logBytes(bytes memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); } - function logBytes1(bytes1 p0) internal view { + function logBytes1(bytes1 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); } - function logBytes2(bytes2 p0) internal view { + function logBytes2(bytes2 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); } - function logBytes3(bytes3 p0) internal view { + function logBytes3(bytes3 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); } - function logBytes4(bytes4 p0) internal view { + function logBytes4(bytes4 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); } - function logBytes5(bytes5 p0) internal view { + function logBytes5(bytes5 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); } - function logBytes6(bytes6 p0) internal view { + function logBytes6(bytes6 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); } - function logBytes7(bytes7 p0) internal view { + function logBytes7(bytes7 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); } - function logBytes8(bytes8 p0) internal view { + function logBytes8(bytes8 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); } - function logBytes9(bytes9 p0) internal view { + function logBytes9(bytes9 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); } - function logBytes10(bytes10 p0) internal view { + function logBytes10(bytes10 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); } - function logBytes11(bytes11 p0) internal view { + function logBytes11(bytes11 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); } - function logBytes12(bytes12 p0) internal view { + function logBytes12(bytes12 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); } - function logBytes13(bytes13 p0) internal view { + function logBytes13(bytes13 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); } - function logBytes14(bytes14 p0) internal view { + function logBytes14(bytes14 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); } - function logBytes15(bytes15 p0) internal view { + function logBytes15(bytes15 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); } - function logBytes16(bytes16 p0) internal view { + function logBytes16(bytes16 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); } - function logBytes17(bytes17 p0) internal view { + function logBytes17(bytes17 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); } - function logBytes18(bytes18 p0) internal view { + function logBytes18(bytes18 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); } - function logBytes19(bytes19 p0) internal view { + function logBytes19(bytes19 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); } - function logBytes20(bytes20 p0) internal view { + function logBytes20(bytes20 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); } - function logBytes21(bytes21 p0) internal view { + function logBytes21(bytes21 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); } - function logBytes22(bytes22 p0) internal view { + function logBytes22(bytes22 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); } - function logBytes23(bytes23 p0) internal view { + function logBytes23(bytes23 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); } - function logBytes24(bytes24 p0) internal view { + function logBytes24(bytes24 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); } - function logBytes25(bytes25 p0) internal view { + function logBytes25(bytes25 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); } - function logBytes26(bytes26 p0) internal view { + function logBytes26(bytes26 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); } - function logBytes27(bytes27 p0) internal view { + function logBytes27(bytes27 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); } - function logBytes28(bytes28 p0) internal view { + function logBytes28(bytes28 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); } - function logBytes29(bytes29 p0) internal view { + function logBytes29(bytes29 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); } - function logBytes30(bytes30 p0) internal view { + function logBytes30(bytes30 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); } - function logBytes31(bytes31 p0) internal view { + function logBytes31(bytes31 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); } - function logBytes32(bytes32 p0) internal view { + function logBytes32(bytes32 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); } - function log(uint256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + function log(uint256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); + } + + function log(int256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } - function log(string memory p0) internal view { + function log(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } - function log(bool p0) internal view { + function log(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } - function log(address p0) internal view { + function log(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } - function log(uint256 p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint)", p0, p1)); + function log(uint256 p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1)); + } + + function log(uint256 p0, string memory p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1)); } - function log(uint256 p0, string memory p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string)", p0, p1)); + function log(uint256 p0, bool p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1)); } - function log(uint256 p0, bool p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool)", p0, p1)); + function log(uint256 p0, address p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1)); } - function log(uint256 p0, address p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address)", p0, p1)); + function log(string memory p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); } - function log(string memory p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint)", p0, p1)); + function log(string memory p0, int256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,int256)", p0, p1)); } - function log(string memory p0, string memory p1) internal view { + function log(string memory p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); } - function log(string memory p0, bool p1) internal view { + function log(string memory p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); } - function log(string memory p0, address p1) internal view { + function log(string memory p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); } - function log(bool p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint)", p0, p1)); + function log(bool p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1)); } - function log(bool p0, string memory p1) internal view { + function log(bool p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); } - function log(bool p0, bool p1) internal view { + function log(bool p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); } - function log(bool p0, address p1) internal view { + function log(bool p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); } - function log(address p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint)", p0, p1)); + function log(address p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1)); } - function log(address p0, string memory p1) internal view { + function log(address p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); } - function log(address p0, bool p1) internal view { + function log(address p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); } - function log(address p0, address p1) internal view { + function log(address p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); } - function log(uint256 p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2)); + function log(uint256 p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2)); + function log(uint256 p0, string memory p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2)); + function log(uint256 p0, string memory p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2)); + function log(uint256 p0, string memory p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2)); } - function log(uint256 p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2)); + function log(uint256 p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2)); } - function log(uint256 p0, bool p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2)); + function log(uint256 p0, bool p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2)); } - function log(uint256 p0, bool p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2)); + function log(uint256 p0, bool p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2)); } - function log(uint256 p0, bool p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2)); + function log(uint256 p0, bool p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2)); } - function log(uint256 p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2)); + function log(uint256 p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2)); } - function log(uint256 p0, address p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2)); + function log(uint256 p0, address p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2)); } - function log(uint256 p0, address p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2)); + function log(uint256 p0, address p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2)); } - function log(uint256 p0, address p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2)); + function log(uint256 p0, address p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2)); + function log(string memory p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2)); + function log(string memory p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2)); + function log(string memory p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2)); + function log(string memory p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2)); } - function log(string memory p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2)); + function log(string memory p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2)); } - function log(string memory p0, string memory p1, string memory p2) internal view { + function log(string memory p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); } - function log(string memory p0, string memory p1, bool p2) internal view { + function log(string memory p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); } - function log(string memory p0, string memory p1, address p2) internal view { + function log(string memory p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); } - function log(string memory p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2)); + function log(string memory p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2)); } - function log(string memory p0, bool p1, string memory p2) internal view { + function log(string memory p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); } - function log(string memory p0, bool p1, bool p2) internal view { + function log(string memory p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); } - function log(string memory p0, bool p1, address p2) internal view { + function log(string memory p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); } - function log(string memory p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2)); + function log(string memory p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2)); } - function log(string memory p0, address p1, string memory p2) internal view { + function log(string memory p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); } - function log(string memory p0, address p1, bool p2) internal view { + function log(string memory p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); } - function log(string memory p0, address p1, address p2) internal view { + function log(string memory p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); } - function log(bool p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2)); + function log(bool p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2)); } - function log(bool p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2)); + function log(bool p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2)); } - function log(bool p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2)); + function log(bool p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2)); } - function log(bool p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2)); + function log(bool p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2)); } - function log(bool p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2)); + function log(bool p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2)); } - function log(bool p0, string memory p1, string memory p2) internal view { + function log(bool p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); } - function log(bool p0, string memory p1, bool p2) internal view { + function log(bool p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); } - function log(bool p0, string memory p1, address p2) internal view { + function log(bool p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); } - function log(bool p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2)); + function log(bool p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2)); } - function log(bool p0, bool p1, string memory p2) internal view { + function log(bool p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); } - function log(bool p0, bool p1, bool p2) internal view { + function log(bool p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); } - function log(bool p0, bool p1, address p2) internal view { + function log(bool p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); } - function log(bool p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2)); + function log(bool p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2)); } - function log(bool p0, address p1, string memory p2) internal view { + function log(bool p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); } - function log(bool p0, address p1, bool p2) internal view { + function log(bool p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); } - function log(bool p0, address p1, address p2) internal view { + function log(bool p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); } - function log(address p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2)); + function log(address p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2)); } - function log(address p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2)); + function log(address p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2)); } - function log(address p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2)); + function log(address p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2)); } - function log(address p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2)); + function log(address p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2)); } - function log(address p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2)); + function log(address p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2)); } - function log(address p0, string memory p1, string memory p2) internal view { + function log(address p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); } - function log(address p0, string memory p1, bool p2) internal view { + function log(address p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); } - function log(address p0, string memory p1, address p2) internal view { + function log(address p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); } - function log(address p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2)); + function log(address p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2)); } - function log(address p0, bool p1, string memory p2) internal view { + function log(address p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); } - function log(address p0, bool p1, bool p2) internal view { + function log(address p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); } - function log(address p0, bool p1, address p2) internal view { + function log(address p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); } - function log(address p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2)); + function log(address p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2)); } - function log(address p0, address p1, string memory p2) internal view { + function log(address p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); } - function log(address p0, address p1, bool p2) internal view { + function log(address p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); } - function log(address p0, address p1, address p2) internal view { + function log(address p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { + function log(string memory p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { + function log(string memory p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, address p3) internal view { + function log(string memory p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { + function log(string memory p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, bool p3) internal view { + function log(string memory p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, address p3) internal view { + function log(string memory p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, string memory p3) internal view { + function log(string memory p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, bool p3) internal view { + function log(string memory p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, address p3) internal view { + function log(string memory p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { + function log(string memory p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, bool p3) internal view { + function log(string memory p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, address p3) internal view { + function log(string memory p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, string memory p3) internal view { + function log(string memory p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, bool p3) internal view { + function log(string memory p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, address p3) internal view { + function log(string memory p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, string memory p3) internal view { + function log(string memory p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, bool p3) internal view { + function log(string memory p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, address p3) internal view { + function log(string memory p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, string memory p3) internal view { + function log(string memory p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, bool p3) internal view { + function log(string memory p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, address p3) internal view { + function log(string memory p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, string memory p3) internal view { + function log(string memory p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, bool p3) internal view { + function log(string memory p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, address p3) internal view { + function log(string memory p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, string memory p3) internal view { + function log(string memory p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, bool p3) internal view { + function log(string memory p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, address p3) internal view { + function log(string memory p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { + function log(bool p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, bool p3) internal view { + function log(bool p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, address p3) internal view { + function log(bool p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, string memory p3) internal view { + function log(bool p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, bool p3) internal view { + function log(bool p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, address p3) internal view { + function log(bool p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, string memory p3) internal view { + function log(bool p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, bool p3) internal view { + function log(bool p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, address p3) internal view { + function log(bool p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, string memory p3) internal view { + function log(bool p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, bool p3) internal view { + function log(bool p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, address p3) internal view { + function log(bool p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, string memory p3) internal view { + function log(bool p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, bool p3) internal view { + function log(bool p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, address p3) internal view { + function log(bool p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, string memory p3) internal view { + function log(bool p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, bool p3) internal view { + function log(bool p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, address p3) internal view { + function log(bool p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, string memory p3) internal view { + function log(bool p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, bool p3) internal view { + function log(bool p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, address p3) internal view { + function log(bool p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, string memory p3) internal view { + function log(bool p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, bool p3) internal view { + function log(bool p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, address p3) internal view { + function log(bool p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, string memory p3) internal view { + function log(bool p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, bool p3) internal view { + function log(bool p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, address p3) internal view { + function log(bool p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, string memory p3) internal view { + function log(address p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, bool p3) internal view { + function log(address p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, address p3) internal view { + function log(address p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, string memory p3) internal view { + function log(address p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, bool p3) internal view { + function log(address p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, address p3) internal view { + function log(address p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, string memory p3) internal view { + function log(address p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, bool p3) internal view { + function log(address p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, address p3) internal view { + function log(address p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, string memory p3) internal view { + function log(address p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, bool p3) internal view { + function log(address p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, address p3) internal view { + function log(address p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, string memory p3) internal view { + function log(address p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, bool p3) internal view { + function log(address p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, address p3) internal view { + function log(address p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, string memory p3) internal view { + function log(address p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, bool p3) internal view { + function log(address p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, address p3) internal view { + function log(address p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, string memory p3) internal view { + function log(address p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, bool p3) internal view { + function log(address p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, address p3) internal view { + function log(address p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, string memory p3) internal view { + function log(address p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, bool p3) internal view { + function log(address p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, address p3) internal view { + function log(address p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, string memory p3) internal view { + function log(address p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, bool p3) internal view { + function log(address p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, address p3) internal view { + function log(address p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } } diff --git a/testdata/default/repros/Issue3708.t.sol b/testdata/default/repros/Issue3708.t.sol index 1e9a337f19..53a7c461f8 100644 --- a/testdata/default/repros/Issue3708.t.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -11,8 +11,7 @@ contract Issue3708Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { - string memory RPC_URL = "https://mainnet.optimism.io"; - uint256 forkId = vm.createSelectFork(RPC_URL); + uint256 forkId = vm.createSelectFork("optimism"); bytes memory code = hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; diff --git a/testdata/default/repros/Issue7238.t.sol b/testdata/default/repros/Issue7238.t.sol new file mode 100644 index 0000000000..73befa3eaa --- /dev/null +++ b/testdata/default/repros/Issue7238.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Reverter { + function doNotRevert() public {} + + function revertWithMessage(string calldata message) public { + revert(message); + } +} + +// https://github.com/foundry-rs/foundry/issues/7238 +contract Issue7238Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testExpectRevertString() public { + Reverter reverter = new Reverter(); + vm.expectRevert("revert"); + reverter.revertWithMessage("revert"); + } + + // FAIL + function testFailRevertNotOnImmediateNextCall() public { + Reverter reverter = new Reverter(); + // expectRevert should only work for the next call. However, + // we do not inmediately revert, so, + // we fail. + vm.expectRevert("revert"); + reverter.doNotRevert(); + reverter.revertWithMessage("revert"); + } + + // FAIL + function testFailCheatcodeRevert() public { + // This expectRevert is hanging, as the next cheatcode call is ignored. + vm.expectRevert(); + vm.fsMetadata("something/something"); // try to go to some non-existent path to cause a revert + } + + function testFailEarlyRevert() public { + vm.expectRevert(); + rever(); + } + + function rever() internal { + revert(); + } +} diff --git a/testdata/default/repros/Issue7457.t.sol b/testdata/default/repros/Issue7457.t.sol index 1836c48254..d95f79c483 100644 --- a/testdata/default/repros/Issue7457.t.sol +++ b/testdata/default/repros/Issue7457.t.sol @@ -61,6 +61,7 @@ contract Issue7457Test is DSTest, ITarget { target.emitAnonymousEventEmpty(); } + /// forge-config: default.allow_internal_expect_revert = true function testEmitEventNonIndexedReverts() public { vm.expectEmit(false, false, false, true); vm.expectRevert("use vm.expectEmitAnonymous to match anonymous events"); diff --git a/testdata/default/repros/Issue9643.t.sol b/testdata/default/repros/Issue9643.t.sol new file mode 100644 index 0000000000..7a985138dc --- /dev/null +++ b/testdata/default/repros/Issue9643.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Mock { + uint256 private counter; + + function setCounter(uint256 _counter) external { + counter = _counter; + } +} + +contract DelegateProxy { + address internal implementation; + + constructor(address mock) { + implementation = mock; + } + + fallback() external payable { + address addr = implementation; + + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), addr, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } +} + +contract Issue9643Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + 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\"}}}}", + rawDiff + ); + } +} diff --git a/testdata/paris/cheats/LastCallGas.t.sol b/testdata/paris/cheats/LastCallGas.t.sol index bc7ac42639..23f6df2249 100644 --- a/testdata/paris/cheats/LastCallGas.t.sol +++ b/testdata/paris/cheats/LastCallGas.t.sol @@ -39,7 +39,7 @@ abstract contract LastCallGasFixture is DSTest { } function testRevertNoCachedLastCallGas() public { - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.lastCallGas(); } diff --git a/testdata/zk/Cheatcodes.t.sol b/testdata/zk/Cheatcodes.t.sol index b8b48c9d96..2ec0f652c6 100644 --- a/testdata/zk/Cheatcodes.t.sol +++ b/testdata/zk/Cheatcodes.t.sol @@ -33,6 +33,16 @@ contract Mock { } } +contract Reverter { + function revertWithMessage() public pure { + revert("test"); + } + + function nestedRevert(Reverter other) public pure { + other.revertWithMessage(); + } +} + interface IMyProxyCaller { function transact(uint8 _data) external; } @@ -206,6 +216,23 @@ contract ZkCheatcodesTest is DSTest { emitter.functionEmit(); } + function testExpectRevert() public { + vm.expectRevert(); + revert("test"); + } + + function testFailExpectRevertDeeperDepthsWithDefaultConfig() public { + Reverter reverter = new Reverter(); + vm.expectRevert(); + reverter.nestedRevert(reverter); + } + + function testExpectRevertDeeperDepthsWithInternalRevertsEnabled() public { + Reverter reverter = new Reverter(); + vm.expectRevert(); + reverter.nestedRevert(reverter); + } + function testZkCheatcodesValueFunctionMockReturn() public { InnerMock inner = new InnerMock(); // Send some funds to so it can pay for the inner call