diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py index a71ff3683..51fc69123 100755 --- a/.github/scripts/matrices.py +++ b/.github/scripts/matrices.py @@ -65,6 +65,7 @@ def __init__( self.partition = partition +profile = os.environ.get("PROFILE") is_pr = os.environ.get("EVENT_NAME") == "pull_request" t_linux_x86 = Target("ubuntu-latest", "x86_64-unknown-linux-gnu", "linux-amd64") # TODO: Figure out how to make this work @@ -119,6 +120,9 @@ def main(): s = f"{partition}/{case.n_partitions}" name += f" ({s})" flags += f" --partition count:{s}" + + if profile == "isolate": + flags += " --features=isolate-by-default" name += os_str obj = Expanded( diff --git a/.github/scripts/prune-prereleases.js b/.github/scripts/prune-prereleases.js index bbdb0696f..d0d6bf465 100644 --- a/.github/scripts/prune-prereleases.js +++ b/.github/scripts/prune-prereleases.js @@ -36,7 +36,7 @@ module.exports = async ({ github, context }) => { // Pruning rules: // 1. only keep the earliest (by created_at) release of the month - // 2. to keep the newest 3 nightlies + // 2. to keep the newest 30 nightlies (to make sure nightlies are kept until the next monthly release) // Notes: // - This addresses https://github.com/foundry-rs/foundry/issues/6732 // - Name of the release may deviate from created_at due to the usage of different timezones. @@ -47,7 +47,7 @@ module.exports = async ({ github, context }) => { const groups = groupBy(nightlies, i => i.created_at.slice(0, 7)); const nightliesToPrune = Object.values(groups) .reduce((acc, cur) => acc.concat(cur.slice(0, -1)), []) // rule 1 - .slice(3); // rule 2 + .slice(30); // rule 2 for (const nightly of nightliesToPrune) { console.log(`Deleting nightly: ${nightly.tag_name}`); diff --git a/.github/workflows/bump-forge-std.yml b/.github/workflows/bump-forge-std.yml new file mode 100644 index 000000000..137e8c465 --- /dev/null +++ b/.github/workflows/bump-forge-std.yml @@ -0,0 +1,25 @@ +# Daily CI job to update forge-std version used for tests if new release has been published + +name: bump-forge-std + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + update-tag: + name: update forge-std tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Fetch and update forge-std tag + run: curl 'https://api.github.com/repos/foundry-rs/forge-std/tags' | jq '.[0].commit.sha' -jr > testdata/forge-std-rev + - name: Create pull request + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "chore: bump forge-std version used for tests" + title: "chore(tests): bump forge-std version" + body: | + New release of forge-std has been published, bump forge-std version used in tests. Likely some fixtures need to be updated. + branch: chore/bump-forge-std diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 72de0dd7a..758c8e589 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -2,10 +2,10 @@ name: deny on: push: - branches: [dev] + branches: [main] paths: [Cargo.lock, deny.toml] pull_request: - branches: [dev] + branches: [main] paths: [Cargo.lock, deny.toml] env: diff --git a/.github/workflows/infrastructure.yml b/.github/workflows/infrastructure.yml index 04c3f0c88..c8f9ab87b 100644 --- a/.github/workflows/infrastructure.yml +++ b/.github/workflows/infrastructure.yml @@ -3,10 +3,10 @@ name: Infrastructure tests on: push: branches: - - dev + - main pull_request: branches: - - dev + - main env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml new file mode 100644 index 000000000..880354080 --- /dev/null +++ b/.github/workflows/nextest.yml @@ -0,0 +1,96 @@ +# Reusable workflow for running tests via `cargo nextest` + +name: nextest + +on: + workflow_call: + inputs: + profile: + required: true + type: string + +concurrency: + group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + matrices: + name: build matrices + runs-on: ubuntu-latest + outputs: + test-matrix: ${{ steps.gen.outputs.test-matrix }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Generate matrices + id: gen + env: + EVENT_NAME: ${{ github.event_name }} + PROFILE: ${{ inputs.profile }} + run: | + output=$(python3 .github/scripts/matrices.py) + echo "::debug::test-matrix=$output" + echo "test-matrix=$output" >> $GITHUB_OUTPUT + + test: + name: test ${{ matrix.name }} + runs-on: ${{ matrix.runner_label }} + timeout-minutes: 60 + needs: matrices + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} + env: + ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD + CARGO_PROFILE_DEV_DEBUG: 0 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ matrix.target }} + - uses: taiki-e/install-action@nextest + + # External tests dependencies + - name: Setup Node.js + if: contains(matrix.name, 'external') + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install Bun + if: contains(matrix.name, 'external') && !contains(matrix.runner_label, 'windows') + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Setup Python + if: contains(matrix.name, 'external') + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Install Vyper + if: contains(matrix.name, 'external') + run: pip install vyper~=0.4.0 + + - name: Forge RPC cache + uses: actions/cache@v3 + with: + path: | + ~/.foundry/cache + ~/.config/.foundry/cache + key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Setup Git config + run: | + git config --global user.name "GitHub Actions Bot" + git config --global user.email "<>" + git config --global url."https://github.com/".insteadOf "git@github.com:" + - name: Test + env: + SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} + run: cargo nextest run ${{ matrix.flags }} diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml new file mode 100644 index 000000000..119a6bd55 --- /dev/null +++ b/.github/workflows/test-isolate.yml @@ -0,0 +1,14 @@ +# Daily CI job to run tests with isolation mode enabled by default + +name: test-isolate + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + nextest: + uses: ./.github/workflows/nextest.yml + with: + profile: isolate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adbe72804..d09ca0342 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,10 @@ name: test on: push: branches: - - dev + - main pull_request: branches: - - dev + - main concurrency: cancel-in-progress: true diff --git a/Cargo.lock b/Cargo.lock index f4612b1ee..bcaa8c141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" +checksum = "f58047cc851e58c26224521d1ecda466e3d746ebca0274cd5427aa660a88c353" dependencies = [ "alloy-eips", "alloy-primitives", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c26b7d34cb76f826558e9409a010e25257f7bfb5aa5e3dd0042c564664ae159" +checksum = "fa5d42d9f87896536234b0fac1a84ad9d9dc7a4b27839cac35d0899e64ddf083" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" +checksum = "413902aa18a97569e60f679c23f46a18db1656d87ab4d4e49d0e1e52042f66df" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -140,20 +140,22 @@ dependencies = [ "proptest", "serde", "serde_json", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] name = "alloy-eips" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" +checksum = "d32a3e14fa0d152d00bd8daf605eb74ad397efb0f54bd7155585823dddb4401e" dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-serde", + "arbitrary", "c-kzg", "derive_more 0.99.18", + "k256 0.13.3", "once_cell", "serde", "sha2 0.10.8", @@ -161,9 +163,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48450f9c6f0821c1eee00ed912942492ed4f11dd69532825833de23ecc7a2256" +checksum = "20cb76c8a3913f2466c5488f3a915e3a15d15596bdc935558c1a9be75e9ec508" dependencies = [ "alloy-primitives", "alloy-serde", @@ -172,9 +174,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -184,9 +186,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" +checksum = "0e76a9feec2352c78545d1a37415699817bae8dc41654bd1bfe57d6cdd5433bd" dependencies = [ "alloy-primitives", "serde", @@ -197,9 +199,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" +checksum = "3223d71dc78f464b2743418d0be8b5c894313e272105a6206ad5e867d67b3ce2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -217,9 +219,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" dependencies = [ "alloy-rlp", "arbitrary", @@ -244,9 +246,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" +checksum = "f29da7457d853cb8199ec04b227d5d2ef598be3e59fc2bbad70c8be213292f32" dependencies = [ "alloy-chains", "alloy-consensus", @@ -281,9 +283,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034258dfaa51c278e1f7fcc46e587d10079ec9372866fa48c5df9d908fc1f6b1" +checksum = "f64acfec654ade392cecfa9bba0408eb2a337d55f1b857925da79970cb70f3d6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -317,14 +319,14 @@ checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "alloy-rpc-client" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" +checksum = "f8a9e609524fa31c2c70eb24c0da60796809193ad4787a6dfe6d0db0d3ac112d" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -347,21 +349,35 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dfa1dd3e0bc3a3d89744fba8d1511216e83257160da2cd028a18b7d9c026030" +checksum = "7e5d76f1e8b22f48b7b8f985782b68e7eb3938780e50e8b646a53e41a598cdf5" dependencies = [ + "alloy-rpc-types-anvil", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4282c002a4ae9f57887dae57083fcca6dca09cb6685bf98b8582ea93cb3df97d" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "serde", ] [[package]] name = "alloy-rpc-types-engine" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc40df2dda7561d1406d0bee1d19c8787483a2cf2ee8011c05909475e7bc102d" +checksum = "73445fbc5c02258e3d0d977835c92366a4d91545fd456c3fc8601c61810bc9f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -377,9 +393,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" +checksum = "605fa8462732bb8fd0645a9941e12961e079d45ae6a44634c826f8229c187bdf" dependencies = [ "alloy-consensus", "alloy-eips", @@ -395,9 +411,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d26db98ac320a0d1637faf3e210328c3df3b1998abd7e72343d3857058efe" +checksum = "5f561a8cdd377b6ac3beab805b9df5ec2c7d99bb6139aab23c317f26df6fb346" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -409,9 +425,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971c92989c6a5588d3f6d1e99e5328fba6e68694efbe969d6ec96ae5b9d1037" +checksum = "c06a4bd39910631c11148c5b2c55e2c61f8626affd2a612e382c668d5e5971ce" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -421,20 +437,21 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" +checksum = "15c5b9057acc02aee1b8aac2b5a0729cb0f73d080082c111313e5d1f92a96630" dependencies = [ "alloy-primitives", + "arbitrary", "serde", "serde_json", ] [[package]] name = "alloy-signer" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" +checksum = "37f10592696f4ab8b687d5a8ab55e998a14ea0ca5f8eb20ad74a96ad671bb54a" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -448,9 +465,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9573e8a5339fefc515b3e336fae177e2080225a4ea49cd5ab24de4b0bdc81d" +checksum = "49300a7aecbd28c364fbad6a9f886264f79ff4fed9a67c8caa27c39f99d52b2d" dependencies = [ "alloy-consensus", "alloy-network", @@ -466,9 +483,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4911b3b4e104af7ed40bf51031a6f0f2400788759f6073a5d90003db6bb88fe6" +checksum = "5ce638c267429ea7513be9fffc47d949d1f425a33c8813fc6a145e03b999e79f" dependencies = [ "alloy-consensus", "alloy-network", @@ -484,9 +501,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb31f033976724d10f90633477436f5e3757b04283c475a750a77e82422aa36" +checksum = "5a9450ae05631ac2a5eb180d91d7162bf71ea7a2bb6833cc7c25997e5a11dc38" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -504,9 +521,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87db68d926887393a1d0f9c43833b44446ea29d603291e7b20e5d115f31aa4e3" +checksum = "0b537f3e55f30753578f4623d5f66ddad8fa582af3fa6b15bad23dd1b9775228" dependencies = [ "alloy-consensus", "alloy-network", @@ -524,9 +541,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c0c55911ca291f842f7d18a06a993679fe672d5d02049c665fa01aafa2b31a" +checksum = "c6efa624373339e7cbdd597a785a69c5fcbc10d5368797a18b7cb3476eadf8c9" dependencies = [ "alloy-consensus", "alloy-network", @@ -541,23 +558,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -567,16 +584,16 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "syn-solidity", "tiny-keccak 2.0.2", ] [[package]] name = "alloy-sol-macro-input" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" dependencies = [ "alloy-json-abi", "const-hex", @@ -585,24 +602,25 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.68", + "syn 2.0.71", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" dependencies = [ - "winnow 0.6.13", + "serde", + "winnow 0.6.14", ] [[package]] name = "alloy-sol-types" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -613,9 +631,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" +checksum = "5b44b0f6f4a2593b258fa7b6cae8968e6a4c404d9ef4f5bc74401f2d04fa23fa" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -626,14 +644,15 @@ dependencies = [ "thiserror", "tokio", "tower", + "tracing", "url", ] [[package]] name = "alloy-transport-http" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" +checksum = "6d8f1eefa8cb9e7550740ee330feba4fed303a77ad3085707546f9152a88c380" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -646,9 +665,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb40ee66887a66d875a5bb5e01cee4c9a467c263ef28c865cd4b0ebf15f705af" +checksum = "31007c56dc65bd81392112dda4a14c20ac7e30bb4cb2e9176192e8d9fab1983f" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -667,15 +686,15 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d92049d6642a18c9849ce7659430151e7c92b51552a0cabdc038c1af4cd7308" +checksum = "15ccc1c8f8ae415e93ec0e7851bd4cdf4afdd48793d13a91b860317da1f36104" dependencies = [ "alloy-pubsub", "alloy-transport", "futures 0.3.30", "http 1.1.0", - "rustls 0.23.10", + "rustls 0.23.11", "serde_json", "tokio", "tokio-tungstenite 0.23.1", @@ -734,10 +753,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "ansi_term" -version = "0.12.1" +name = "ansiterm" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "4ab587f5395da16dd2e6939adf53dede583221b320cadfb94e02b5b7b9bf24cc" dependencies = [ "winapi", ] @@ -810,7 +829,6 @@ dependencies = [ "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", - "alloy-rpc-types-trace", "alloy-serde", "alloy-signer", "alloy-signer-local", @@ -840,7 +858,7 @@ dependencies = [ "foundry-evm", "foundry-test-utils", "futures 0.3.30", - "hyper 1.4.0", + "hyper 1.4.1", "itertools 0.13.0", "k256 0.13.3", "parking_lot 0.12.3", @@ -871,7 +889,6 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-rpc-types-trace", "alloy-serde", "alloy-trie", "bytes", @@ -1129,7 +1146,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1151,18 +1168,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1218,7 +1235,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1249,7 +1266,7 @@ dependencies = [ "fastrand", "hex", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "ring 0.17.8", "time", "tokio", @@ -1290,14 +1307,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] name = "aws-sdk-kms" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e9940bfbfded74ea7172fe75815ce2b2aed4766d2375620df812b2aeab8eaa" +checksum = "6bf39203c9fa4b177c5b58ebf19fc97bbfece1e17c3171c7c5e356ca5f6ea42c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1317,9 +1334,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcfae7bf8b8f14cade7579ffa8956fcee91dc23633671096b4b5de7d16f682a" +checksum = "fc3ef4ee9cdd19ec6e8b10d963b79637844bbf41c31177b77a188eaa941e69f7" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1339,9 +1356,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b30def8f02ba81276d5dbc22e7bf3bed20d62d1b175eef82680d6bdc7a6f4c" +checksum = "527f3da450ea1f09f95155dba6153bd0d83fe0923344a12e1944dfa5d0b32064" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1361,9 +1378,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0804f840ad31537d5d1a4ec48d59de5e674ad05f1db7d3def2c9acadaf1f7e60" +checksum = "94316606a4aa2cb7a302388411b8776b3fbd254e8506e2dc43918286d8212e9b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1457,9 +1474,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df4217d39fe940066174e6238310167bf466bfbebf3be0661e53cacccde6313" +checksum = "ce87155eba55e11768b8c1afa607f3e864ae82f03caf63258b37455b0ad02537" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1470,9 +1487,9 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", @@ -1511,7 +1528,7 @@ dependencies = [ "http 0.2.12", "http 1.1.0", "http-body 0.4.6", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "itoa", "num-integer", @@ -1558,7 +1575,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "itoa", "matchit", "memchr", @@ -1585,9 +1602,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-util", "itoa", "matchit", @@ -1637,7 +1654,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -1790,9 +1807,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1917,6 +1934,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff 0.13.0", + "group 0.13.0", + "pairing", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "blst" version = "0.3.12" @@ -1979,7 +2009,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "syn_derive", ] @@ -2063,9 +2093,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "byteorder" @@ -2075,9 +2105,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" dependencies = [ "serde", ] @@ -2205,7 +2235,6 @@ dependencies = [ "clap_complete", "clap_complete_fig", "comfy-table", - "const-hex", "criterion", "dunce", "evm-disassembler", @@ -2254,13 +2283,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -2507,9 +2535,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -2517,14 +2545,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", "unicase", "unicode-width", @@ -2532,9 +2560,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d" +checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae" dependencies = [ "clap", ] @@ -2558,7 +2586,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3217,14 +3245,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -3232,27 +3260,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", - "syn 2.0.68", + "strsim", + "syn 2.0.71", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3292,7 +3320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] @@ -3344,7 +3372,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3365,7 +3393,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3375,7 +3403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3388,7 +3416,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3408,7 +3436,7 @@ checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "unicode-xid", ] @@ -3516,7 +3544,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3745,7 +3773,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3824,7 +3852,7 @@ dependencies = [ "once_cell", "openssl-sys", "reqwest 0.11.27", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "sha3 0.10.8", @@ -4071,8 +4099,8 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.68", - "toml 0.8.14", + "syn 2.0.71", + "toml 0.8.15", "walkdir", ] @@ -4089,7 +4117,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -4115,7 +4143,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.68", + "syn 2.0.71", "tempfile", "thiserror", "tiny-keccak 2.0.2", @@ -4391,6 +4419,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec 1.0.1", "rand_core 0.6.4", "subtle", ] @@ -4440,7 +4469,7 @@ dependencies = [ "pear", "serde", "tempfile", - "toml 0.8.14", + "toml 0.8.15", "uncased", "version_check", ] @@ -4574,7 +4603,6 @@ dependencies = [ "clap_complete_fig", "clearscreen", "comfy-table", - "const-hex", "criterion", "dialoguer", "dunce", @@ -4594,6 +4622,7 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-evm-abi", "foundry-linking", "foundry-test-utils", "foundry-wallets", @@ -4602,7 +4631,7 @@ dependencies = [ "futures 0.3.30", "globset", "humantime-serde", - "hyper 1.4.0", + "hyper 1.4.1", "indicatif", "itertools 0.13.0", "jsonrpc-core", @@ -4618,7 +4647,7 @@ dependencies = [ "regex", "reqwest 0.12.5", "revm-inspectors", - "rustc-hash", + "rustc-hash 2.0.0", "semver 1.0.23", "serde", "serde_json", @@ -4632,8 +4661,8 @@ dependencies = [ "thiserror", "tikv-jemallocator", "tokio", - "toml 0.8.14", - "toml_edit 0.22.14", + "toml 0.8.15", + "toml_edit 0.22.16", "tower-http", "tracing", "tracing-subscriber", @@ -4666,7 +4695,7 @@ dependencies = [ "serde_json", "solang-parser", "thiserror", - "toml 0.8.14", + "toml 0.8.15", "tracing", ] @@ -4681,7 +4710,7 @@ dependencies = [ "similar-asserts", "solang-parser", "thiserror", - "toml 0.8.14", + "toml 0.8.15", "tracing", "tracing-subscriber", ] @@ -4703,7 +4732,6 @@ dependencies = [ "alloy-transport", "async-recursion", "clap", - "const-hex", "dialoguer", "dunce", "eyre", @@ -4746,7 +4774,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -4759,7 +4787,6 @@ dependencies = [ "alloy-rpc-types", "async-trait", "clap", - "const-hex", "eyre", "foundry-block-explorers", "foundry-cli", @@ -4795,8 +4822,8 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.4.1" -source = "git+https://github.com/Moonsong-Labs/block-explorers?branch=zksync-v0.4.1#9dd59fc58a1a200355c2554d88c06386a9ce2db1" +version = "0.5.1" +source = "git+https://github.com/Moonsong-Labs/block-explorers?branch=zksync-v0.5.1#8100b9c7aa2e1d5242521393969cd34db6d53503" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -4824,7 +4851,6 @@ dependencies = [ "alloy-signer-local", "alloy-sol-types", "base64 0.22.1", - "const-hex", "dialoguer", "eyre", "foundry-cheatcodes-common", @@ -4841,13 +4867,14 @@ dependencies = [ "k256 0.13.3", "p256", "parking_lot 0.12.3", + "proptest", "rand 0.8.5", "revm", - "rustc-hash", + "rustc-hash 2.0.0", "semver 1.0.23", "serde_json", "thiserror", - "toml 0.8.14", + "toml 0.8.15", "tracing", "walkdir", "zksync_types", @@ -4884,7 +4911,6 @@ dependencies = [ "alloy-transport", "clap", "color-eyre", - "const-hex", "dotenvy", "eyre", "forge-fmt", @@ -4899,13 +4925,13 @@ dependencies = [ "once_cell", "regex", "serde", - "strsim 0.10.0", + "strsim", "strum 0.26.3", "tempfile", "tokio", "tracing", - "tracing-error", "tracing-subscriber", + "tracing-tracy", "yansi 1.0.1", ] @@ -4913,7 +4939,6 @@ dependencies = [ name = "foundry-common" version = "0.0.2" dependencies = [ - "alloy-consensus", "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", @@ -4923,7 +4948,6 @@ dependencies = [ "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-rpc-types-engine", "alloy-serde", "alloy-sol-types", "alloy-transport", @@ -4931,25 +4955,21 @@ dependencies = [ "alloy-transport-ipc", "alloy-transport-ws", "async-trait", - "chrono", "clap", "comfy-table", - "derive_more 0.99.18", "dunce", "eyre", "foundry-block-explorers", + "foundry-common-fmt", "foundry-compilers", "foundry-config", "foundry-linking", - "foundry-macros", "foundry-zksync-compiler", "globset", - "itertools 0.13.0", "num-format", "once_cell", "reqwest 0.12.5", - "revm", - "rustc-hash", + "rustc-hash 2.0.0", "semver 1.0.23", "serde", "serde_json", @@ -4963,10 +4983,29 @@ dependencies = [ "yansi 1.0.1", ] +[[package]] +name = "foundry-common-fmt" +version = "0.0.2" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-primitives", + "alloy-rpc-types", + "alloy-serde", + "chrono", + "comfy-table", + "foundry-macros", + "revm-primitives", + "serde", + "serde_json", + "similar-asserts", + "yansi 1.0.1", +] + [[package]] name = "foundry-compilers" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4998,14 +5037,14 @@ dependencies = [ "thiserror", "tokio", "tracing", - "winnow 0.6.13", + "winnow 0.6.14", "yansi 1.0.1", ] [[package]] name = "foundry-compilers-artifacts" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -5014,8 +5053,8 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -5027,6 +5066,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", + "serde_repr", "thiserror", "tokio", "tracing", @@ -5036,20 +5076,22 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "alloy-json-abi", "alloy-primitives", "foundry-compilers-artifacts-solc", + "foundry-compilers-core", "path-slash", + "semver 1.0.23", "serde", ] [[package]] name = "foundry-compilers-artifacts-zksolc" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -5069,8 +5111,8 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.8.0" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#e4afd97fc5a5f1b48b279c0f90b53388503dbb5b" +version = "0.10.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.10.0#07b1b367b28affacc5542c59053c4dff67f26507" dependencies = [ "alloy-primitives", "cfg-if 1.0.0", @@ -5119,8 +5161,8 @@ dependencies = [ "solang-parser", "tempfile", "thiserror", - "toml 0.8.14", - "toml_edit 0.22.14", + "toml 0.8.15", + "toml_edit 0.22.16", "tracing", "walkdir", ] @@ -5134,11 +5176,11 @@ dependencies = [ "eyre", "foundry-common", "foundry-compilers", - "foundry-evm-core", "foundry-evm-traces", "ratatui", "revm", "revm-inspectors", + "serde", "tracing", ] @@ -5150,7 +5192,6 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-types", - "arrayvec 0.7.4", "eyre", "foundry-cheatcodes", "foundry-common", @@ -5171,6 +5212,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "foundry-evm-abi" +version = "0.0.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "derive_more 0.99.18", + "foundry-common-fmt", + "foundry-macros", + "foundry-test-utils", + "itertools 0.13.0", + "once_cell", + "rustc-hash 2.0.0", +] + [[package]] name = "foundry-evm-core" version = "0.0.2" @@ -5184,30 +5240,26 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "alloy-transport", - "arrayvec 0.7.4", "auto_impl", - "const-hex", - "derive_more 0.99.18", "eyre", "foundry-cheatcodes-spec", "foundry-common", "foundry-config", - "foundry-macros", + "foundry-evm-abi", + "foundry-fork-db", "foundry-test-utils", "foundry-zksync-core", "futures 0.3.30", "itertools 0.13.0", - "once_cell", "parking_lot 0.12.3", "revm", "revm-inspectors", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "serde_json", "thiserror", "tokio", "tracing", - "url", ] [[package]] @@ -5221,7 +5273,7 @@ dependencies = [ "foundry-evm-core", "rayon", "revm", - "rustc-hash", + "rustc-hash 2.0.0", "semver 1.0.23", "tracing", ] @@ -5261,7 +5313,6 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-types", - "const-hex", "eyre", "foundry-block-explorers", "foundry-cheatcodes-spec", @@ -5269,16 +5320,42 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm-core", + "foundry-linking", "futures 0.3.30", "itertools 0.13.0", "once_cell", + "rayon", + "revm", "revm-inspectors", - "rustc-hash", + "rustc-hash 2.0.0", "serde", + "solang-parser", "tempfile", "tokio", "tracing", - "yansi 1.0.1", +] + +[[package]] +name = "foundry-fork-db" +version = "0.2.0" +source = "git+https://github.com/Moonsong-Labs/foundry-zksync-fork-db?branch=zksync-v0.2.0#5fac51ff6b286051eedec1d37a7af065b632c37d" +dependencies = [ + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-transport", + "eyre", + "futures 0.3.30", + "parking_lot 0.12.3", + "revm", + "rustc-hash 2.0.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", ] [[package]] @@ -5298,7 +5375,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -5318,6 +5395,7 @@ dependencies = [ "regex", "serde_json", "similar-asserts", + "snapbox", "tracing", "tracing-subscriber", "walkdir", @@ -5342,7 +5420,6 @@ dependencies = [ "aws-config", "aws-sdk-kms", "clap", - "const-hex", "derive_builder", "eyre", "foundry-config", @@ -5383,12 +5460,12 @@ dependencies = [ "alloy-signer", "alloy-sol-types", "alloy-transport", - "ansi_term", - "const-hex", + "ansiterm", "era_test_node", "eyre", "foundry-cheatcodes-common", "foundry-common", + "foundry-evm-abi", "foundry-zksync-compiler", "itertools 0.13.0", "lazy_static", @@ -5559,7 +5636,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -5625,7 +5702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d460327b24cc34c86d53d60a90e9e6044817f7906ebd9baa5c3d0ee13e1ecf" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.30", "serde", "serde_json", "thiserror", @@ -5643,11 +5720,11 @@ dependencies = [ "chrono", "futures 0.3.30", "gcemeta", - "hyper 0.14.29", + "hyper 0.14.30", "jsonwebtoken 9.3.0", "once_cell", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", "reqwest 0.11.27", "secret-vault-value", "serde", @@ -5661,6 +5738,20 @@ dependencies = [ "url", ] +[[package]] +name = "generator" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "libc", + "log", + "rustversion", + "windows 0.54.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -5713,7 +5804,7 @@ dependencies = [ "gix-utils", "itoa", "thiserror", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] @@ -5734,7 +5825,7 @@ dependencies = [ "smallvec", "thiserror", "unicode-bom", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] @@ -5836,7 +5927,7 @@ dependencies = [ "itoa", "smallvec", "thiserror", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] @@ -5871,7 +5962,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] @@ -6190,7 +6281,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -6228,9 +6319,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -6245,7 +6336,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -6285,9 +6376,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -6309,15 +6400,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -6335,7 +6426,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", @@ -6351,9 +6442,9 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-util", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-native-certs 0.7.1", "rustls-pki-types", "tokio", @@ -6368,7 +6459,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -6381,7 +6472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", @@ -6395,7 +6486,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-util", "native-tls", "tokio", @@ -6413,8 +6504,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.4.0", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "socket2", "tokio", @@ -6808,7 +6899,7 @@ version = "18.0.0" source = "git+https://github.com/matter-labs/jsonrpc.git?branch=master#12c53e3e20c09c2fb9966a4ef1b0ea63de172540" dependencies = [ "futures 0.3.30", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpc-core", "jsonrpc-server-utils", "log", @@ -6902,12 +6993,12 @@ dependencies = [ "beef", "futures-timer", "futures-util", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpsee-types", "parking_lot 0.12.3", "pin-project 1.1.5", "rand 0.8.5", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "thiserror", @@ -6924,7 +7015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b7de9f3219d95985eb77fd03194d7c1b56c19bce1abfcc9d07462574b15572" dependencies = [ "async-trait", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "jsonrpsee-core", "jsonrpsee-types", @@ -6958,7 +7049,7 @@ checksum = "5cc7c6d1a2c58f6135810284a390d9f823d0f508db74cd914d8237802de80f98" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpsee-core", "jsonrpsee-types", "pin-project 1.1.5", @@ -7105,6 +7196,21 @@ dependencies = [ "libc", ] +[[package]] +name = "kzg-rs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9920cd4460ce3cbca19c62f3bb9a9611562478a4dc9d2c556f4a7d049c5b6b" +dependencies = [ + "bls12_381", + "glob", + "hex", + "once_cell", + "serde", + "serde_derive", + "serde_yaml", +] + [[package]] name = "lalrpop" version = "0.20.2" @@ -7168,9 +7274,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if 1.0.0", "windows-targets 0.52.6", @@ -7259,7 +7365,7 @@ checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7310,7 +7416,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7322,6 +7428,19 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if 1.0.0", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.3" @@ -7333,9 +7452,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9764018d143cc854c9f17f0b907de70f14393b1f502da6375dce70f00514eb3" +checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" dependencies = [ "cc", "libc", @@ -7497,7 +7616,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7508,7 +7627,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7593,7 +7712,7 @@ dependencies = [ "cfg-if 1.0.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7710,6 +7829,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "normalize-path" version = "0.2.1" @@ -7911,7 +8036,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7920,10 +8045,10 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7971,9 +8096,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" @@ -8041,7 +8166,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8241,6 +8366,15 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group 0.13.0", +] + [[package]] name = "pairing_ce" version = "0.28.5" @@ -8379,7 +8513,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -8449,7 +8583,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8523,7 +8657,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8607,7 +8741,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8665,7 +8799,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8747,9 +8881,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "powerfmt" @@ -8802,7 +8936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8919,7 +9053,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "version_check", "yansi 1.0.1", ] @@ -8935,7 +9069,7 @@ dependencies = [ "nix 0.28.0", "tokio", "tracing", - "windows", + "windows 0.56.0", ] [[package]] @@ -8957,9 +9091,9 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", @@ -8975,7 +9109,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -9029,6 +9163,16 @@ dependencies = [ "prost-derive 0.12.6", ] +[[package]] +name = "prost" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +dependencies = [ + "bytes", + "prost-derive 0.13.1", +] + [[package]] name = "prost-build" version = "0.12.6" @@ -9044,9 +9188,9 @@ dependencies = [ "petgraph", "prettyplease", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", "regex", - "syn 2.0.68", + "syn 2.0.71", "tempfile", ] @@ -9073,7 +9217,20 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", +] + +[[package]] +name = "prost-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.71", ] [[package]] @@ -9087,7 +9244,7 @@ dependencies = [ "miette 5.10.0", "once_cell", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", "serde", "serde-value", ] @@ -9101,6 +9258,15 @@ dependencies = [ "prost 0.12.6", ] +[[package]] +name = "prost-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +dependencies = [ + "prost 0.13.1", +] + [[package]] name = "protobuf" version = "3.3.0" @@ -9131,7 +9297,7 @@ dependencies = [ "miette 5.10.0", "prost 0.12.6", "prost-reflect", - "prost-types", + "prost-types 0.12.6", "protox-parse", "thiserror", ] @@ -9144,7 +9310,7 @@ checksum = "7b4581f441c58863525a3e6bec7b8de98188cf75239a56c725a3e7288450a33f" dependencies = [ "logos", "miette 5.10.0", - "prost-types", + "prost-types 0.12.6", "thiserror", ] @@ -9231,8 +9397,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", - "rustls 0.23.10", + "rustc-hash 1.1.0", + "rustls 0.23.11", "thiserror", "tokio", "tracing", @@ -9247,8 +9413,8 @@ dependencies = [ "bytes", "rand 0.8.5", "ring 0.17.8", - "rustc-hash", - "rustls 0.23.10", + "rustc-hash 1.1.0", + "rustls 0.23.11", "slab", "thiserror", "tinyvec", @@ -9482,9 +9648,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -9574,7 +9740,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "hyper-tls 0.5.0", "ipnet", @@ -9620,9 +9786,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-rustls 0.27.2", "hyper-tls 0.6.0", "hyper-util", @@ -9636,7 +9802,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-native-certs 0.7.1", "rustls-pemfile 2.1.2", "rustls-pki-types", @@ -9660,8 +9826,9 @@ dependencies = [ [[package]] name = "revm" -version = "9.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "12.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cfb48bce8ca2113e157bdbddbd5eeb09daac1c903d79ec17085897c38c7c91" dependencies = [ "auto_impl", "cfg-if 1.0.0", @@ -9674,8 +9841,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=4fe17f0#4fe17f08797450d9d5df315e724d14c9f3749b3f" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2dc001e37ac3b061dc9087876aea91e28756c188a97cd99416d23a5562ca73" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -9690,8 +9858,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "5.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b0daddea06fc6da5346acc39b32a357bbe3579e9e3d94117d9ae125cd596fc" dependencies = [ "revm-primitives", "serde", @@ -9699,11 +9868,14 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "7.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef55228211251d7b6c7707c3ee13bb70dea4d2fd81ec4034521e4fe31010b2ea" dependencies = [ "aurora-engine-modexp", + "blst", "c-kzg", + "cfg-if 1.0.0", "k256 0.13.3", "once_cell", "p256", @@ -9716,9 +9888,11 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc4311037ee093ec50ec734e1424fcb3e12d535c6cef683b75d1c064639630c" dependencies = [ + "alloy-eips", "alloy-primitives", "auto_impl", "bitflags 2.6.0", @@ -9730,6 +9904,7 @@ dependencies = [ "enumn", "hashbrown 0.14.5", "hex", + "kzg-rs", "once_cell", "serde", ] @@ -9809,7 +9984,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] @@ -9971,6 +10146,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -10036,9 +10217,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ "once_cell", "ring 0.17.8", @@ -10210,9 +10391,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.2" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af947d0ca10a2f3e00c7ec1b515b7c83e5cb3fa62d4c11a64301d9eec54440e9" +checksum = "a4465c22496331e20eb047ff46e7366455bc01c0c02015c4a376de0b2cd3a1af" dependencies = [ "sdd", ] @@ -10247,9 +10428,15 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.68", + "syn 2.0.71", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -10280,9 +10467,9 @@ dependencies = [ [[package]] name = "sdd" -version = "0.2.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" +checksum = "85f05a494052771fc5bd0619742363b5e24e5ad72ab3111ec2e27925b8edc5f3" [[package]] name = "seahash" @@ -10366,12 +10553,12 @@ dependencies = [ [[package]] name = "secret-vault-value" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f8cfb86d2019f64a4cfb49e499f401f406fbec946c1ffeea9d0504284347de" +checksum = "bc32a777b53b3433b974c9c26b6d502a50037f8da94e46cb8ce2ced2cfdfaea0" dependencies = [ - "prost 0.12.6", - "prost-types", + "prost 0.13.1", + "prost-types 0.13.1", "serde", "serde_json", "zeroize", @@ -10379,9 +10566,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -10392,9 +10579,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -10544,7 +10731,7 @@ dependencies = [ "thiserror", "time", "url", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] @@ -10555,9 +10742,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -10574,13 +10761,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -10591,7 +10778,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -10634,7 +10821,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -10693,7 +10880,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -10977,6 +11164,30 @@ dependencies = [ "serde", ] +[[package]] +name = "snapbox" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d656960fa127e80ade23c321d8c573bb9ba462c3a69e62ede635fc180ffc6cc" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "serde", + "serde_json", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" +dependencies = [ + "anstream", +] + [[package]] name = "socket2" version = "0.5.7" @@ -11019,9 +11230,9 @@ dependencies = [ [[package]] name = "soldeer" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef46372c17d5650cb18b7f374c45732334fa0867de6c7f14c1fc6973559cd3ff" +checksum = "d584c27ebf7ad3e2557d029a07b19fa0d192d67bd73eea1e1695936eb5666e34" dependencies = [ "chrono", "clap", @@ -11037,13 +11248,13 @@ dependencies = [ "sha256", "simple-home-dir", "tokio", - "toml 0.8.14", - "toml_edit 0.22.14", - "uuid 1.9.1", + "toml 0.8.15", + "toml_edit 0.22.16", + "uuid 1.10.0", "walkdir", "yansi 1.0.1", "yash-fnmatch", - "zip 2.1.3", + "zip 2.1.5", "zip-extract", ] @@ -11307,7 +11518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11365,12 +11576,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -11418,7 +11623,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11477,7 +11682,7 @@ dependencies = [ "sha2 0.10.8", "thiserror", "url", - "zip 2.1.3", + "zip 2.1.5", ] [[package]] @@ -11506,9 +11711,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -11517,14 +11722,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11536,7 +11741,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11658,22 +11863,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11778,9 +11983,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -11793,9 +11998,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -11828,7 +12033,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -11868,7 +12073,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pki-types", "tokio", ] @@ -11920,7 +12125,7 @@ checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -11967,15 +12172,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.16", ] [[package]] @@ -12022,15 +12227,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.14", ] [[package]] @@ -12048,7 +12253,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project 1.1.5", @@ -12075,7 +12280,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project 1.1.5", @@ -12128,7 +12333,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", @@ -12187,7 +12392,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -12290,11 +12495,42 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-tracy" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be7f8874d6438e4263f9874c84eded5095bda795d9c7da6ea0192e1750d3ffe" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracy-client", +] + +[[package]] +name = "tracy-client" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63de1e1d4115534008d8fd5788b39324d6f58fc707849090533828619351d855" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + +[[package]] +name = "tracy-client-sys" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b98232a2447ce0a58f9a0bfb5f5e39647b5c597c994b63945fcccd1306fafb" +dependencies = [ + "cc", +] + [[package]] name = "trezor-client" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62c95b37f6c769bd65a0d0beb8b2b003e72998003b896a616a6777c645c05ed" +checksum = "10636211ab89c96ed2824adc5ec0d081e1080aeacc24c37abb318dcb31dcc779" dependencies = [ "byteorder", "hex", @@ -12368,7 +12604,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls 0.23.10", + "rustls 0.23.11", "rustls-pki-types", "sha1", "thiserror", @@ -12474,11 +12710,12 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-truncate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.12.1", + "itertools 0.13.0", + "unicode-segmentation", "unicode-width", ] @@ -12530,9 +12767,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64 0.22.1", "log", @@ -12583,9 +12820,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", "serde", @@ -12605,9 +12842,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.1" +version = "8.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -12641,7 +12878,7 @@ source = "git+https://github.com/matter-labs/vise.git?rev=a5bb80c9ce7168663114ee dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -12734,7 +12971,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -12768,7 +13005,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12939,6 +13176,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.56.0" @@ -12958,6 +13205,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.56.0" @@ -12978,7 +13235,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -12989,7 +13246,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -13151,9 +13408,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" dependencies = [ "memchr", ] @@ -13267,7 +13524,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -13287,7 +13544,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -13312,9 +13569,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.1.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +checksum = "b895748a3ebcb69b9d38dcfdf21760859a4b0d0b0015277640c2ef4c69640e6f" dependencies = [ "arbitrary", "crc32fast", @@ -13921,7 +14178,7 @@ dependencies = [ "prost-reflect", "protox", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -14087,9 +14344,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 4ede27933..30477e035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ resolver = "2" version = "0.0.2" edition = "2021" # Remember to update clippy.toml as well -rust-version = "1.76" +rust-version = "1.79" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" @@ -41,11 +41,14 @@ dbg-macro = "warn" manual-string-new = "warn" uninlined-format-args = "warn" use-self = "warn" +redundant-clone = "warn" +octal-escapes = "allow" [workspace.lints.rust] -rust-2018-idioms = "deny" +rust-2018-idioms = "warn" # unreachable-pub = "warn" -unused-must-use = "deny" +unused-must-use = "warn" +redundant-lifetimes = "warn" [workspace.lints.rustdoc] all = "warn" @@ -54,7 +57,33 @@ all = "warn" # NOTE: Debuggers may provide less useful information with this setting. # Uncomment this section if you're using a debugger. [profile.dev] -debug = false +# https://davidlattimore.github.io/posts/2024/02/04/speeding-up-the-rust-edit-build-run-cycle.html +debug = "line-tables-only" +split-debuginfo = "unpacked" + +[profile.release] +opt-level = 3 +lto = "thin" +debug = "line-tables-only" +strip = "symbols" +panic = "abort" +codegen-units = 16 + +# Use the `--profile profiling` flag to show symbols in release mode. +# e.g. `cargo build --profile profiling` +[profile.profiling] +inherits = "release" +debug = 2 +split-debuginfo = "unpacked" +strip = false + +[profile.bench] +inherits = "profiling" + +[profile.maxperf] +inherits = "release" +lto = "fat" +codegen-units = 1 # Speed up tests and dev build. [profile.dev.package] @@ -92,37 +121,10 @@ axum.opt-level = 3 # keystores scrypt.opt-level = 3 -# Local "release" mode, more optimized than dev but much faster to compile than release. -[profile.local] -inherits = "dev" -opt-level = 1 -debug-assertions = false -overflow-checks = false -strip = "debuginfo" -panic = "abort" -codegen-units = 16 - -# Like release, but with full debug symbols and with stack unwinds. Useful for e.g. `perf`. -[profile.debug-fast] -inherits = "local" -debug = true -strip = "none" -panic = "unwind" - -# Optimized release profile. -[profile.release] -opt-level = 3 -debug = "line-tables-only" -lto = "fat" -strip = "debuginfo" -panic = "abort" -codegen-units = 1 - # Override packages which aren't perf-sensitive for faster compilation speed. [profile.release.package] mdbook.opt-level = 1 protobuf.opt-level = 1 -toml_edit.opt-level = 1 trezor-client.opt-level = 1 [workspace.dependencies] @@ -141,9 +143,11 @@ foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cheatcodes-common = { path = "crates/cheatcodes/common" } foundry-cli = { path = "crates/cli" } foundry-common = { path = "crates/common" } +foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } +foundry-evm-abi = { path = "crates/evm/abi" } foundry-evm-core = { path = "crates/evm/core" } foundry-evm-coverage = { path = "crates/evm/coverage" } foundry-evm-fuzz = { path = "crates/evm/fuzz" } @@ -158,56 +162,55 @@ foundry-zksync-compiler = { path = "crates/zksync/compiler" } # solc & compilation utilities # foundry-block-explorers = { version = "0.4.1", default-features = false } # foundry-compilers = { version = "0.8.0", default-features = false } -foundry-block-explorers = { git = "https://github.com/Moonsong-Labs/block-explorers", branch = "zksync-v0.4.1", default-features = false } -foundry-compilers = { git = "https://github.com/Moonsong-Labs/compilers", branch = "zksync-v0.8.0" } +foundry-block-explorers = { git = "https://github.com/Moonsong-Labs/block-explorers", branch = "zksync-v0.5.1", default-features = false } +foundry-compilers = { git = "https://github.com/Moonsong-Labs/compilers", branch = "zksync-v0.10.0" } +foundry-fork-db = { git = "https://github.com/Moonsong-Labs/foundry-zksync-fork-db", branch = "zksync-v0.2.0" } +solang-parser = "=0.3.3" ## revm # no default features to avoid c-kzg -revm = { version = "9.0.0", default-features = false } -revm-primitives = { version = "4.0.0", default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "4fe17f0", features = [ - "serde", -] } +revm = { version = "12.1.0", default-features = false } +revm-primitives = { version = "7.1.0", default-features = false } +revm-inspectors = { version = "0.5", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { version = "0.1.1", default-features = false } -alloy-contract = { version = "0.1.1", default-features = false } -alloy-eips = { version = "0.1.1", default-features = false } -alloy-genesis = { version = "0.1.1", default-features = false } -alloy-json-rpc = { version = "0.1.1", default-features = false } -alloy-network = { version = "0.1.1", default-features = false } -alloy-node-bindings = { version = "0.1.1", default-features = false } -alloy-provider = { version = "0.1.1", default-features = false } -alloy-pubsub = { version = "0.1.1", default-features = false } -alloy-rpc-client = { version = "0.1.1", default-features = false } -alloy-rpc-types-engine = { version = "0.1.1", default-features = false } -alloy-rpc-types-trace = { version = "0.1.1", default-features = false } -alloy-rpc-types = { version = "0.1.1", default-features = false } -alloy-serde = { version = "0.1.1", default-features = false } -alloy-signer = { version = "0.1.1", default-features = false } -alloy-signer-local = { version = "0.1.1", default-features = false } -alloy-signer-aws = { version = "0.1.1", default-features = false } -alloy-signer-gcp = { version = "0.1.1", default-features = false } -alloy-signer-ledger = { version = "0.1.1", default-features = false } -alloy-signer-trezor = { version = "0.1.1", default-features = false } -alloy-transport = { version = "0.1.1", default-features = false } -alloy-transport-http = { version = "0.1.1", default-features = false } -alloy-transport-ipc = { version = "0.1.1", default-features = false } -alloy-transport-ws = { version = "0.1.1", default-features = false } -alloy-primitives = { version = "0.7.1", features = ["getrandom", "rand"] } -alloy-dyn-abi = "0.7.1" -alloy-json-abi = "0.7.1" -alloy-sol-types = "0.7.1" -alloy-sol-macro-input = "0.7.3" -alloy-sol-macro-expander = "0.7.3" -syn-solidity = "0.7.1" +alloy-consensus = { version = "0.2.0", default-features = false } +alloy-contract = { version = "0.2.0", default-features = false } +alloy-eips = { version = "0.2.0", default-features = false } +alloy-genesis = { version = "0.2.0", default-features = false } +alloy-json-rpc = { version = "0.2.0", default-features = false } +alloy-network = { version = "0.2.0", default-features = false } +alloy-node-bindings = { version = "0.2.0", default-features = false } +alloy-provider = { version = "0.2.0", default-features = false } +alloy-pubsub = { version = "0.2.0", default-features = false } +alloy-rpc-client = { version = "0.2.0", default-features = false } +alloy-rpc-types = { version = "0.2.0", default-features = false } +alloy-serde = { version = "0.2.0", default-features = false } +alloy-signer = { version = "0.2.0", default-features = false } +alloy-signer-aws = { version = "0.2.0", default-features = false } +alloy-signer-gcp = { version = "0.2.0", default-features = false } +alloy-signer-ledger = { version = "0.2.0", default-features = false } +alloy-signer-local = { version = "0.2.0", default-features = false } +alloy-signer-trezor = { version = "0.2.0", default-features = false } +alloy-transport = { version = "0.2.0", default-features = false } +alloy-transport-http = { version = "0.2.0", default-features = false } +alloy-transport-ipc = { version = "0.2.0", default-features = false } +alloy-transport-ws = { version = "0.2.0", default-features = false } + +alloy-dyn-abi = "0.7.7" +alloy-json-abi = "0.7.7" +alloy-primitives = { version = "0.7.7", features = ["getrandom", "rand"] } +alloy-sol-macro-expander = "0.7.7" +alloy-sol-macro-input = "0.7.7" +alloy-sol-types = "0.7.7" +syn-solidity = "0.7.7" + alloy-chains = "0.1" -alloy-trie = "0.4.1" alloy-rlp = "0.3.3" -solang-parser = "=0.3.3" +alloy-trie = "0.4.1" ## zksync era_test_node = { git="https://github.com/matter-labs/era-test-node.git" , rev = "dd6d2f463eb9697dc2365899a72ae12dae3ec809" } @@ -229,7 +232,6 @@ quote = "1.0" syn = "2.0" prettyplease = "0.2.20" ahash = "0.8" -arrayvec = "0.7" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = [ "clock", @@ -242,14 +244,13 @@ evm-disassembler = "0.5" eyre = "0.6" figment = "0.10" futures = "0.3" -hex = { package = "const-hex", version = "1.6", features = ["hex"] } itertools = "0.13" jsonpath_lib = "0.3" k256 = "0.13" once_cell = "1" parking_lot = "0.12" rand = "0.8" -rustc-hash = "1.1" +rustc-hash = "2.0" semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } @@ -275,13 +276,10 @@ reqwest = { version = "0.12", default-features = false } tower = "0.4" tower-http = "0.5" # soldeer -soldeer = "0.2.15" +soldeer = "0.2.19" -[patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +proptest = "1" +comfy-table = "7" # revm-interpreter = { path = "../revm/crates/interpreter" } # revm-primitives = { path = "../revm/crates/primitives" } @@ -310,4 +308,4 @@ revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7 # foundry-compilers-artifacts-vyper = { path = "../msl-compilers/crates/artifacts/vyper" } # foundry-compilers-artifacts-solc = { path = "../msl-compilers/crates/artifacts/solc" } # foundry-compilers-artifacts-zksolc = { path = "../msl-compilers/crates/artifacts/zksolc" } -# foundry-compilers-artifacts = { path = "../msl-compilers/crates/artifacts/artifacts" } \ No newline at end of file +# foundry-compilers-artifacts = { path = "../msl-compilers/crates/artifacts/artifacts" } diff --git a/Dockerfile b/Dockerfile index 48e97b7e6..6fcf8ff27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,9 +18,6 @@ COPY . . # see RUN git update-index --force-write-index -# This is necessary to compile librocksdb-sys -ENV RUSTFLAGS -Ctarget-feature=-crt-static - RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/opt/foundry/target \ source $HOME/.profile && cargo build --release --features cast/aws-kms,forge/aws-kms \ && mkdir out \ diff --git a/README.md b/README.md index 7c020d69a..8f511369d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,19 @@ To use for zkSync environments, include `--zksync` when running `forge` or `vm.z ### Features `Foundry-zksync` offers a set of features designed to work with zkSync Era, providing a comprehensive toolkit for smart contract deployment and interaction: +- **Fast & flexible compilation pipeline** + - Automatic Solidity compiler version detection & installation + - **Incremental compilation & caching**: Only changed files are re-compiled + - Parallel compilation + - Non-standard directory structures support (e.g. [Hardhat repos](https://twitter.com/gakonst/status/1461289225337421829)) +- **Tests are written in Solidity** (like in DappTools) +- **Fast fuzz testing** with shrinking of inputs & printing of counter-examples +- **Fast remote RPC forking mode**, leveraging Rust's async infrastructure like tokio +- **Flexible debug logging** + - DappTools-style, using `DsTest`'s emitted logs + - Hardhat-style, using the popular `console.sol` contract +- **Portable (5-10MB) & easy to install** without requiring Nix or any other package manager +- **Fast CI** with the [Foundry GitHub action][foundry-gha]. - **Smart Contract Deployment**: Easily deploy smart contracts to zkSync Era mainnet, testnet, or a local test node. - **Contract Interaction**: Call and send transactions to deployed contracts on zkSync Era testnet or local test node. diff --git a/clippy.toml b/clippy.toml index 09acb653d..b5a99df72 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.76" +msrv = "1.79" # bytes::Bytes is included by default and alloy_primitives::Bytes is a wrapper around it, # so it is safe to ignore it as well ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index c374a8c8c..a98787985 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -54,8 +54,7 @@ alloy-signer = { workspace = true, features = ["eip712"] } alloy-signer-local = { workspace = true, features = ["mnemonic"] } alloy-sol-types = { workspace = true, features = ["std"] } alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } -alloy-rpc-types = { workspace = true, features = ["txpool"] } -alloy-rpc-types-trace.workspace = true +alloy-rpc-types = { workspace = true, features = ["anvil", "trace", "txpool"] } alloy-serde.workspace = true alloy-provider = { workspace = true, features = [ "reqwest", @@ -76,7 +75,7 @@ tower.workspace = true # tracing tracing.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } # async tokio = { workspace = true, features = ["time"] } @@ -89,7 +88,7 @@ flate2 = "1.0" serde_repr = "0.1" serde_json.workspace = true serde.workspace = true -thiserror .workspace = true +thiserror.workspace = true yansi.workspace = true tempfile.workspace = true itertools.workspace = true diff --git a/crates/anvil/README.md b/crates/anvil/README.md index 0e775441d..efb0f9e2e 100644 --- a/crates/anvil/README.md +++ b/crates/anvil/README.md @@ -5,7 +5,7 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** ## Features - Network forking: fork any EVM-compatible blockchain, same as in `forge` -- [Ethereum JSON-RPC](https://eth.wiki/json-rpc/API) support +- [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 @@ -27,9 +27,7 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** ### Installing from source ```sh -git clone https://github.com/foundry-rs/foundry -cd foundry -cargo install --path ./crates/anvil --profile local --locked --force +cargo install --git https://github.com/foundry-rs/foundry anvil --locked --force ``` ## Getting started diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index aa7a323ed..8c2720c8f 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -23,8 +23,7 @@ revm = { workspace = true, default-features = false, features = [ ] } alloy-primitives = { workspace = true, features = ["serde"] } -alloy-rpc-types.workspace = true -alloy-rpc-types-trace.workspace = true +alloy-rpc-types = { workspace = true, features = ["anvil", "trace"] } alloy-serde.workspace = true alloy-rlp.workspace = true alloy-eips.workspace = true diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index d2fa41a7b..64844d76b 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,15 +1,16 @@ -use crate::{ - eth::subscription::SubscriptionId, - types::{EvmMineOptions, Forking, Index}, -}; +use crate::eth::subscription::SubscriptionId; use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256}; use alloy_rpc_types::{ + anvil::{Forking, MineOptions}, pubsub::{Params as SubscriptionParams, SubscriptionKind}, request::TransactionRequest, state::StateOverride, - BlockId, BlockNumberOrTag as BlockNumber, Filter, + trace::{ + filter::TraceFilter, + geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, + }, + BlockId, BlockNumberOrTag as BlockNumber, Filter, Index, }; -use alloy_rpc_types_trace::geth::{GethDebugTracingOptions, GethDefaultTracingOptions}; use alloy_serde::WithOtherFields; pub mod block; @@ -85,6 +86,9 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "eth_getBalance"))] EthGetBalance(Address, Option), + #[cfg_attr(feature = "serde", serde(rename = "eth_getAccount"))] + EthGetAccount(Address, Option), + #[cfg_attr(feature = "serde", serde(rename = "eth_getStorageAt"))] EthGetStorageAt(Address, U256, Option), @@ -146,6 +150,11 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "eth_sign", alias = "personal_sign"))] EthSign(Address, Bytes), + /// The sign method calculates an Ethereum specific signature, equivalent to eth_sign: + /// + #[cfg_attr(feature = "serde", serde(rename = "personal_sign"))] + PersonalSign(Bytes, Address), + #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction", with = "sequence"))] EthSignTransaction(Box>), @@ -294,7 +303,7 @@ pub enum EthRequest { DebugTraceCall( WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, - #[cfg_attr(feature = "serde", serde(default))] GethDefaultTracingOptions, + #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingCallOptions, ), /// Trace transaction endpoint for parity's `trace_transaction` @@ -311,6 +320,10 @@ pub enum EthRequest { )] TraceBlock(BlockNumber), + // Return filtered traces over blocks + #[cfg_attr(feature = "serde", serde(rename = "trace_filter",))] + TraceFilter(TraceFilter), + // Custom endpoints, they're not extracted to a separate type out of serde convenience /// send transactions impersonating specific account and contract addresses. #[cfg_attr( @@ -609,7 +622,7 @@ pub enum EthRequest { /// Mine a single block #[cfg_attr(feature = "serde", serde(rename = "evm_mine"))] - EvmMine(#[cfg_attr(feature = "serde", serde(default))] Option>>), + EvmMine(#[cfg_attr(feature = "serde", serde(default))] Option>>), /// Mine a single block and return detailed data /// @@ -620,7 +633,7 @@ pub enum EthRequest { serde(rename = "anvil_mine_detailed", alias = "evm_mine_detailed",) )] EvmMineDetailed( - #[cfg_attr(feature = "serde", serde(default))] Option>>, + #[cfg_attr(feature = "serde", serde(default))] Option>>, ), /// Execute a transaction regardless of signature status @@ -1292,7 +1305,7 @@ mod tests { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), - EvmMineOptions::Options { timestamp: Some(100), blocks: Some(100) } + MineOptions::Options { timestamp: Some(100), blocks: Some(100) } ) } _ => unreachable!(), @@ -1329,7 +1342,7 @@ mod tests { EthRequest::EvmMineDetailed(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), - EvmMineOptions::Options { timestamp: Some(100), blocks: Some(100) } + MineOptions::Options { timestamp: Some(100), blocks: Some(100) } ) } _ => unreachable!(), @@ -1360,7 +1373,7 @@ mod tests { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), - EvmMineOptions::Timestamp(Some(1672937224)) + MineOptions::Timestamp(Some(1672937224)) ) } _ => unreachable!(), @@ -1373,7 +1386,7 @@ mod tests { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), - EvmMineOptions::Options { timestamp: Some(1672937224), blocks: None } + MineOptions::Options { timestamp: Some(1672937224), blocks: None } ) } _ => unreachable!(), @@ -1564,7 +1577,7 @@ true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "personal_sign", "params": -["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; +["0x00", "0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 83db64465..5aeb2cd0d 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -2,36 +2,34 @@ use crate::eth::transaction::optimism::{DepositTransaction, DepositTransactionRequest}; use alloy_consensus::{ - transaction::eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, + transaction::{ + eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, + TxEip7702, + }, AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, - TxEnvelope, TxLegacy, TxReceipt, + TxEnvelope, TxLegacy, TxReceipt, TxType, }; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; -use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256, U64}; +use alloy_primitives::{ + Address, Bloom, Bytes, Log, Parity, Signature, TxHash, TxKind, B256, U256, U64, +}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use alloy_rpc_types::{ - request::TransactionRequest, AccessList, AnyTransactionReceipt, Signature as RpcSignature, - Transaction as RpcTransaction, TransactionReceipt, + request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, AnyTransactionReceipt, + ConversionError, Signature as RpcSignature, Transaction as RpcTransaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; use bytes::BufMut; use foundry_evm::traces::CallTraceNode; use revm::{ interpreter::InstructionResult, - primitives::{OptimismFields, TransactTo, TxEnv}, + primitives::{OptimismFields, TxEnv}, }; use serde::{Deserialize, Serialize}; use std::ops::{Deref, Mul}; pub mod optimism; -/// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC -#[cfg(feature = "impersonated-tx")] -pub fn impersonated_signature() -> Signature { - Signature::from_scalars_and_parity(B256::with_last_byte(1), B256::with_last_byte(1), false) - .unwrap() -} - /// Converts a [TransactionRequest] into a [TypedTransactionRequest]. /// Should be removed once the call builder abstraction for providers is in place. pub fn transaction_request_to_typed( @@ -70,7 +68,7 @@ pub fn transaction_request_to_typed( gas_limit: gas.unwrap_or_default(), is_system_tx: other.get_deserialized::("isSystemTx")?.ok()?, input: input.into_input().unwrap_or_default(), - })) + })); } match ( @@ -174,7 +172,7 @@ pub enum TypedTransactionRequest { /// /// This is a helper that carries the `impersonated` sender so that the right hash /// [TypedTransaction::impersonated_hash] can be created. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct MaybeImpersonatedTransaction { pub transaction: TypedTransaction, pub impersonated_sender: Option
, @@ -198,21 +196,28 @@ impl MaybeImpersonatedTransaction { #[cfg(feature = "impersonated-tx")] pub fn recover(&self) -> Result { if let Some(sender) = self.impersonated_sender { - return Ok(sender) + return Ok(sender); } self.transaction.recover() } + /// Returns whether the transaction is impersonated + /// + /// Note: this is feature gated so it does not conflict with the `Deref`ed + /// [TypedTransaction::hash] function by default. + #[cfg(feature = "impersonated-tx")] + pub fn is_impersonated(&self) -> bool { + self.impersonated_sender.is_some() + } + /// Returns the hash of the transaction /// /// Note: this is feature gated so it does not conflict with the `Deref`ed /// [TypedTransaction::hash] function by default. #[cfg(feature = "impersonated-tx")] pub fn hash(&self) -> B256 { - if self.transaction.is_impersonated() { - if let Some(sender) = self.impersonated_sender { - return self.transaction.impersonated_hash(sender) - } + if let Some(sender) = self.impersonated_sender { + return self.transaction.impersonated_hash(sender) } self.transaction.hash() } @@ -277,7 +282,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( block_number: None, transaction_index: None, from, - to: None, + to: t.tx().to.to().copied(), value: t.tx().value, gas_price: Some(t.tx().gas_price), max_fee_per_gas: Some(t.tx().gas_price), @@ -288,7 +293,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), - v: U256::from(t.signature().v().y_parity_byte()), + v: U256::from(t.signature().v().to_u64()), y_parity: None, }), access_list: None, @@ -296,6 +301,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( max_fee_per_blob_gas: None, blob_versioned_hashes: None, other: Default::default(), + authorization_list: None, }, TypedTransaction::EIP2930(t) => RpcTransaction { hash, @@ -304,7 +310,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( block_number: None, transaction_index: None, from, - to: None, + to: t.tx().to.to().copied(), value: t.tx().value, gas_price: Some(t.tx().gas_price), max_fee_per_gas: Some(t.tx().gas_price), @@ -315,7 +321,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), - v: U256::from(t.signature().v().y_parity_byte()), + v: U256::from(t.signature().v().to_u64()), y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), }), access_list: Some(t.tx().access_list.clone()), @@ -323,6 +329,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( max_fee_per_blob_gas: None, blob_versioned_hashes: None, other: Default::default(), + authorization_list: None, }, TypedTransaction::EIP1559(t) => RpcTransaction { hash, @@ -331,7 +338,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( block_number: None, transaction_index: None, from, - to: None, + to: t.tx().to.to().copied(), value: t.tx().value, gas_price: None, max_fee_per_gas: Some(t.tx().max_fee_per_gas), @@ -342,7 +349,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), - v: U256::from(t.signature().v().y_parity_byte()), + v: U256::from(t.signature().v().to_u64()), y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), }), access_list: Some(t.tx().access_list.clone()), @@ -350,6 +357,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( max_fee_per_blob_gas: None, blob_versioned_hashes: None, other: Default::default(), + authorization_list: None, }, TypedTransaction::EIP4844(t) => RpcTransaction { hash, @@ -358,7 +366,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( block_number: None, transaction_index: None, from, - to: None, + to: Some(t.tx().tx().to), value: t.tx().tx().value, gas_price: Some(t.tx().tx().max_fee_per_gas), max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), @@ -369,7 +377,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), - v: U256::from(t.signature().v().y_parity_byte()), + v: U256::from(t.signature().v().to_u64()), y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), }), access_list: Some(t.tx().tx().access_list.clone()), @@ -377,6 +385,33 @@ pub fn to_alloy_transaction_with_hash_and_sender( max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), other: Default::default(), + authorization_list: None, + }, + TypedTransaction::EIP7702(t) => RpcTransaction { + hash, + nonce: t.tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: t.tx().to.to().copied(), + value: t.tx().value, + gas_price: Some(t.tx().max_fee_per_gas), + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: Some(t.tx().chain_id), + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().to_u64()), + y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), + }), + access_list: Some(t.tx().access_list.clone()), + transaction_type: Some(4), + authorization_list: Some(t.tx().authorization_list.clone()), + ..Default::default() }, TypedTransaction::Deposit(t) => RpcTransaction { hash, @@ -385,7 +420,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( block_number: None, transaction_index: None, from, - to: None, + to: t.kind.to().copied(), value: t.value, gas_price: None, max_fee_per_gas: None, @@ -399,6 +434,7 @@ pub fn to_alloy_transaction_with_hash_and_sender( max_fee_per_blob_gas: None, blob_versioned_hashes: None, other: Default::default(), + authorization_list: None, }, } } @@ -446,10 +482,10 @@ impl PendingTransaction { /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) /// expects. pub fn to_revm_tx_env(&self) -> TxEnv { - fn transact_to(kind: &TxKind) -> TransactTo { + fn transact_to(kind: &TxKind) -> TxKind { match kind { - TxKind::Call(c) => TransactTo::Call(*c), - TxKind::Create => TransactTo::Create, + TxKind::Call(c) => TxKind::Call(*c), + TxKind::Create => TxKind::Create, } } @@ -494,7 +530,7 @@ impl PendingTransaction { gas_price: U256::from(*gas_price), gas_priority_fee: None, gas_limit: *gas_limit as u64, - access_list: access_list.flattened(), + access_list: access_list.clone().into(), ..Default::default() } } @@ -521,7 +557,7 @@ impl PendingTransaction { gas_price: U256::from(*max_fee_per_gas), gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), gas_limit: *gas_limit as u64, - access_list: access_list.flattened(), + access_list: access_list.clone().into(), ..Default::default() } } @@ -542,7 +578,7 @@ impl PendingTransaction { } = tx.tx().tx(); TxEnv { caller, - transact_to: TransactTo::call(*to), + transact_to: TxKind::Call(*to), data: input.clone(), chain_id: Some(*chain_id), nonce: Some(*nonce), @@ -552,7 +588,35 @@ impl PendingTransaction { max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), blob_hashes: blob_versioned_hashes.clone(), gas_limit: *gas_limit as u64, - access_list: access_list.flattened(), + access_list: access_list.clone().into(), + ..Default::default() + } + } + TypedTransaction::EIP7702(tx) => { + let TxEip7702 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + authorization_list, + input, + } = tx.tx(); + TxEnv { + caller, + transact_to: *to, + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*max_fee_per_gas), + gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + gas_limit: *gas_limit as u64, + access_list: access_list.clone().into(), + authorization_list: Some(authorization_list.clone().into()), ..Default::default() } } @@ -604,6 +668,8 @@ pub enum TypedTransaction { EIP1559(Signed), /// EIP-4844 transaction EIP4844(Signed), + /// EIP-7702 transaction + EIP7702(Signed), /// op-stack deposit transaction Deposit(DepositTransaction), } @@ -611,7 +677,7 @@ pub enum TypedTransaction { impl TypedTransaction { /// Returns true if the transaction uses dynamic fees: EIP1559 or EIP4844 pub fn is_dynamic_fee(&self) -> bool { - matches!(self, Self::EIP1559(_)) || matches!(self, Self::EIP4844(_)) + matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_)) } pub fn gas_price(&self) -> u128 { @@ -620,6 +686,7 @@ impl TypedTransaction { Self::EIP2930(tx) => tx.tx().gas_price, Self::EIP1559(tx) => tx.tx().max_fee_per_gas, Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas, + Self::EIP7702(tx) => tx.tx().max_fee_per_gas, Self::Deposit(_) => 0, } } @@ -630,6 +697,7 @@ impl TypedTransaction { Self::EIP2930(tx) => tx.tx().gas_limit, Self::EIP1559(tx) => tx.tx().gas_limit, Self::EIP4844(tx) => tx.tx().tx().gas_limit, + Self::EIP7702(tx) => tx.tx().gas_limit, Self::Deposit(tx) => tx.gas_limit, } } @@ -640,6 +708,7 @@ impl TypedTransaction { Self::EIP2930(tx) => tx.tx().value, Self::EIP1559(tx) => tx.tx().value, Self::EIP4844(tx) => tx.tx().tx().value, + Self::EIP7702(tx) => tx.tx().value, Self::Deposit(tx) => tx.value, }) } @@ -650,6 +719,7 @@ impl TypedTransaction { Self::EIP2930(tx) => &tx.tx().input, Self::EIP1559(tx) => &tx.tx().input, Self::EIP4844(tx) => &tx.tx().tx().input, + Self::EIP7702(tx) => &tx.tx().input, Self::Deposit(tx) => &tx.input, } } @@ -661,6 +731,7 @@ impl TypedTransaction { Self::EIP2930(_) => Some(1), Self::EIP1559(_) => Some(2), Self::EIP4844(_) => Some(3), + Self::EIP7702(_) => Some(4), Self::Deposit(_) => Some(0x7E), } } @@ -703,7 +774,7 @@ impl TypedTransaction { input: t.tx().input.clone(), nonce: t.tx().nonce, gas_limit: t.tx().gas_limit, - gas_price: Some(U256::from(t.tx().gas_price)), + gas_price: Some(t.tx().gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, max_fee_per_blob_gas: None, @@ -717,7 +788,7 @@ impl TypedTransaction { input: t.tx().input.clone(), nonce: t.tx().nonce, gas_limit: t.tx().gas_limit, - gas_price: Some(U256::from(t.tx().gas_price)), + gas_price: Some(t.tx().gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, max_fee_per_blob_gas: None, @@ -732,8 +803,8 @@ impl TypedTransaction { nonce: t.tx().nonce, gas_limit: t.tx().gas_limit, gas_price: None, - max_fee_per_gas: Some(U256::from(t.tx().max_fee_per_gas)), - max_priority_fee_per_gas: Some(U256::from(t.tx().max_priority_fee_per_gas)), + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), max_fee_per_blob_gas: None, blob_versioned_hashes: None, value: t.tx().value, @@ -745,21 +816,35 @@ impl TypedTransaction { input: t.tx().tx().input.clone(), nonce: t.tx().tx().nonce, gas_limit: t.tx().tx().gas_limit, - gas_price: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), - max_fee_per_gas: Some(U256::from(t.tx().tx().max_fee_per_gas)), - max_priority_fee_per_gas: Some(U256::from(t.tx().tx().max_priority_fee_per_gas)), - max_fee_per_blob_gas: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), + gas_price: Some(t.tx().tx().max_fee_per_blob_gas), + max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas), + max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), value: t.tx().tx().value, chain_id: Some(t.tx().tx().chain_id), access_list: t.tx().tx().access_list.clone(), }, + Self::EIP7702(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(t.tx().max_fee_per_gas), + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, Self::Deposit(t) => TransactionEssentials { kind: t.kind, input: t.input.clone(), nonce: t.nonce, gas_limit: t.gas_limit, - gas_price: Some(U256::from(0)), + gas_price: Some(0), max_fee_per_gas: None, max_priority_fee_per_gas: None, max_fee_per_blob_gas: None, @@ -777,6 +862,7 @@ impl TypedTransaction { Self::EIP2930(t) => t.tx().nonce, Self::EIP1559(t) => t.tx().nonce, Self::EIP4844(t) => t.tx().tx().nonce, + Self::EIP7702(t) => t.tx().nonce, Self::Deposit(t) => t.nonce, } } @@ -787,6 +873,7 @@ impl TypedTransaction { Self::EIP2930(t) => Some(t.tx().chain_id), Self::EIP1559(t) => Some(t.tx().chain_id), Self::EIP4844(t) => Some(t.tx().tx().chain_id), + Self::EIP7702(t) => Some(t.tx().chain_id), Self::Deposit(t) => t.chain_id(), } } @@ -828,16 +915,11 @@ impl TypedTransaction { Self::EIP2930(t) => *t.hash(), Self::EIP1559(t) => *t.hash(), Self::EIP4844(t) => *t.hash(), + Self::EIP7702(t) => *t.hash(), Self::Deposit(t) => t.hash(), } } - /// Returns true if the transaction was impersonated (using the impersonate Signature) - #[cfg(feature = "impersonated-tx")] - pub fn is_impersonated(&self) -> bool { - self.signature() == impersonated_signature() - } - /// Returns the hash if the transaction is impersonated (using a fake signature) /// /// This appends the `address` before hashing it @@ -856,6 +938,7 @@ impl TypedTransaction { Self::EIP2930(tx) => tx.recover_signer(), Self::EIP1559(tx) => tx.recover_signer(), Self::EIP4844(tx) => tx.recover_signer(), + Self::EIP7702(tx) => tx.recover_signer(), Self::Deposit(tx) => tx.recover(), } } @@ -867,6 +950,7 @@ impl TypedTransaction { Self::EIP2930(tx) => tx.tx().to, Self::EIP1559(tx) => tx.tx().to, Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), + Self::EIP7702(tx) => tx.tx().to, Self::Deposit(tx) => tx.kind, } } @@ -883,32 +967,121 @@ impl TypedTransaction { Self::EIP2930(tx) => *tx.signature(), Self::EIP1559(tx) => *tx.signature(), Self::EIP4844(tx) => *tx.signature(), + Self::EIP7702(tx) => *tx.signature(), Self::Deposit(_) => Signature::from_scalars_and_parity( B256::with_last_byte(1), B256::with_last_byte(1), - false, + Parity::Parity(false), ) .unwrap(), } } } +impl TryFrom for TypedTransaction { + type Error = ConversionError; + + fn try_from(tx: RpcTransaction) -> Result { + // TODO(sergerad): Handle Arbitrum system transactions? + match tx.transaction_type.unwrap_or_default().try_into()? { + TxType::Legacy => { + let legacy = TxLegacy { + chain_id: tx.chain_id, + nonce: tx.nonce, + gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, + gas_limit: tx.gas, + value: tx.value, + input: tx.input, + to: tx.to.map_or(TxKind::Create, TxKind::Call), + }; + let signature = tx + .signature + .ok_or(ConversionError::MissingSignature)? + .try_into() + .map_err(ConversionError::SignatureError)?; + Ok(Self::Legacy(Signed::new_unchecked(legacy, signature, tx.hash))) + } + TxType::Eip1559 => { + let eip1559 = TxEip1559 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + max_fee_per_gas: tx + .max_fee_per_gas + .ok_or(ConversionError::MissingMaxFeePerGas)?, + max_priority_fee_per_gas: tx + .max_priority_fee_per_gas + .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, + gas_limit: tx.gas, + value: tx.value, + input: tx.input, + to: tx.to.map_or(TxKind::Create, TxKind::Call), + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, + }; + let signature = tx + .signature + .ok_or(ConversionError::MissingSignature)? + .try_into() + .map_err(ConversionError::SignatureError)?; + Ok(Self::EIP1559(Signed::new_unchecked(eip1559, signature, tx.hash))) + } + TxType::Eip2930 => { + let eip2930 = TxEip2930 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, + gas_limit: tx.gas, + value: tx.value, + input: tx.input, + to: tx.to.map_or(TxKind::Create, TxKind::Call), + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, + }; + let signature = tx + .signature + .ok_or(ConversionError::MissingSignature)? + .try_into() + .map_err(ConversionError::SignatureError)?; + Ok(Self::EIP2930(Signed::new_unchecked(eip2930, signature, tx.hash))) + } + TxType::Eip4844 => { + let eip4844 = TxEip4844 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + gas_limit: tx.gas, + max_fee_per_gas: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, + max_priority_fee_per_gas: tx + .max_priority_fee_per_gas + .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, + max_fee_per_blob_gas: tx + .max_fee_per_blob_gas + .ok_or(ConversionError::MissingMaxFeePerBlobGas)?, + to: tx.to.ok_or(ConversionError::MissingTo)?, + value: tx.value, + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, + blob_versioned_hashes: tx + .blob_versioned_hashes + .ok_or(ConversionError::MissingBlobVersionedHashes)?, + input: tx.input, + }; + Ok(Self::EIP4844(Signed::new_unchecked( + TxEip4844Variant::TxEip4844(eip4844), + tx.signature + .ok_or(ConversionError::MissingSignature)? + .try_into() + .map_err(ConversionError::SignatureError)?, + tx.hash, + ))) + } + } + } +} + impl Encodable for TypedTransaction { fn encode(&self, out: &mut dyn bytes::BufMut) { - match self { - Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode(out), - Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode(out), - Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode(out), - Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode(out), - Self::Deposit(tx) => { - let tx_payload_len = tx.fields_len(); - let tx_header_len = Header { list: false, payload_length: tx_payload_len }.length(); - Header { list: false, payload_length: 1 + tx_payload_len + tx_header_len } - .encode(out); - out.put_u8(0x7E); - tx.encode(out); - } + if !self.is_legacy() { + Header { list: false, payload_length: self.encode_2718_len() }.encode(out); } + + self.encode_2718(out); } } @@ -919,7 +1092,7 @@ impl Decodable for TypedTransaction { // Legacy TX if header.list { - return Ok(TxEnvelope::decode(buf)?.into()) + return Ok(TxEnvelope::decode(buf)?.into()); } // Check byte after header @@ -944,6 +1117,10 @@ impl Encodable2718 for TypedTransaction { Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP7702(tx) => { + let payload_length = tx.tx().fields_len() + tx.signature().rlp_vrs_len(); + Header { list: true, payload_length }.length() + payload_length + 1 + } Self::Deposit(tx) => 1 + tx.length(), } } @@ -954,6 +1131,7 @@ impl Encodable2718 for TypedTransaction { Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP7702(tx) => tx.tx().encode_with_signature(tx.signature(), out, false), Self::Deposit(tx) => { out.put_u8(0x7E); tx.encode(out); @@ -964,8 +1142,10 @@ impl Encodable2718 for TypedTransaction { impl Decodable2718 for TypedTransaction { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { - if ty == 0x7E { - return Ok(Self::Deposit(DepositTransaction::decode(buf)?)) + match ty { + 0x04 => return Ok(Self::EIP7702(TxEip7702::decode_signed_fields(buf)?)), + 0x7E => return Ok(Self::Deposit(DepositTransaction::decode(buf)?)), + _ => {} } match TxEnvelope::typed_decode(ty, buf)? { TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), @@ -1001,10 +1181,10 @@ pub struct TransactionEssentials { pub input: Bytes, pub nonce: u64, pub gas_limit: u128, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub max_fee_per_blob_gas: Option, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, pub blob_versioned_hashes: Option>, pub value: U256, pub chain_id: Option, @@ -1012,7 +1192,7 @@ pub struct TransactionEssentials { } /// Represents all relevant information of an executed transaction -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct TransactionInfo { pub transaction_hash: B256, pub transaction_index: u64, @@ -1135,6 +1315,8 @@ pub enum TypedReceipt { EIP1559(ReceiptWithBloom), #[serde(rename = "0x3", alias = "0x03")] EIP4844(ReceiptWithBloom), + #[serde(rename = "0x4", alias = "0x04")] + EIP7702(ReceiptWithBloom), #[serde(rename = "0x7E", alias = "0x7e")] Deposit(DepositReceipt), } @@ -1142,12 +1324,49 @@ pub enum TypedReceipt { impl TypedReceipt { pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom { match self { - Self::Legacy(r) | Self::EIP1559(r) | Self::EIP2930(r) | Self::EIP4844(r) => r, + Self::Legacy(r) | + Self::EIP1559(r) | + Self::EIP2930(r) | + Self::EIP4844(r) | + Self::EIP7702(r) => r, Self::Deposit(r) => &r.inner, } } } +impl From> for ReceiptWithBloom { + fn from(value: TypedReceipt) -> Self { + match value { + TypedReceipt::Legacy(r) | + TypedReceipt::EIP1559(r) | + TypedReceipt::EIP2930(r) | + TypedReceipt::EIP4844(r) | + TypedReceipt::EIP7702(r) => r, + TypedReceipt::Deposit(r) => r.inner, + } + } +} + +impl From> for OtsReceipt { + fn from(value: TypedReceipt) -> Self { + let r#type = match value { + TypedReceipt::Legacy(_) => 0x00, + TypedReceipt::EIP2930(_) => 0x01, + TypedReceipt::EIP1559(_) => 0x02, + TypedReceipt::EIP4844(_) => 0x03, + TypedReceipt::EIP7702(_) => 0x04, + TypedReceipt::Deposit(_) => 0x7E, + } as u8; + let receipt = ReceiptWithBloom::::from(value); + let status = receipt.status(); + let cumulative_gas_used = receipt.cumulative_gas_used() as u64; + let logs = receipt.receipt.logs.into_iter().map(|x| x.inner).collect(); + let logs_bloom = receipt.logs_bloom; + + Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type } + } +} + impl TypedReceipt { pub fn cumulative_gas_used(&self) -> u128 { self.as_receipt_with_bloom().cumulative_gas_used() @@ -1267,6 +1486,7 @@ impl Encodable2718 for TypedReceipt { Self::EIP2930(_) => Some(1), Self::EIP1559(_) => Some(2), Self::EIP4844(_) => Some(3), + Self::EIP7702(_) => Some(4), Self::Deposit(_) => Some(0x7E), } } @@ -1277,20 +1497,22 @@ impl Encodable2718 for TypedReceipt { Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(), Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(), Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(), + Self::EIP7702(r) => 1 + r.length(), Self::Deposit(r) => 1 + r.length(), } } fn encode_2718(&self, out: &mut dyn BufMut) { + if let Some(ty) = self.type_flag() { + out.put_u8(ty); + } match self { - Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718(out), - Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718(out), - Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718(out), - Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718(out), - Self::Deposit(r) => { - out.put_u8(0x7E); - r.encode(out); - } + Self::Legacy(r) | + Self::EIP2930(r) | + Self::EIP1559(r) | + Self::EIP4844(r) | + Self::EIP7702(r) => r.encode(out), + Self::Deposit(r) => r.encode(out), } } } @@ -1298,7 +1520,7 @@ impl Encodable2718 for TypedReceipt { impl Decodable2718 for TypedReceipt { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { if ty == 0x7E { - return Ok(Self::Deposit(DepositReceipt::decode(buf)?)) + return Ok(Self::Deposit(DepositReceipt::decode(buf)?)); } match ReceiptEnvelope::typed_decode(ty, buf)? { ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), @@ -1335,6 +1557,7 @@ pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option Option TypedReceipt::Legacy(receipt_with_bloom), 0x01 => TypedReceipt::EIP2930(receipt_with_bloom), @@ -1402,7 +1626,7 @@ mod tests { let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); let tx = TypedTransaction::Legacy(Signed::new_unchecked( - tx.clone(), + tx, signature, b256!("a517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"), )); diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs index 4a147297b..f2e4cff26 100644 --- a/crates/anvil/core/src/eth/transaction/optimism.rs +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -108,7 +108,6 @@ impl DepositTransactionRequest { } /// Calculates a heuristic for the in-memory size of the [DepositTransaction] transaction. - #[inline] pub fn size(&self) -> usize { mem::size_of::() + // source_hash mem::size_of::
() + // from diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index 79a6c7a2c..348686abc 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -1,77 +1,7 @@ -use alloy_primitives::{TxHash, B256, U256, U64}; -use revm::primitives::SpecId; -use std::collections::BTreeMap; +use alloy_primitives::{B256, U256}; #[cfg(feature = "serde")] -use serde::{de::Error, Deserializer, Serializer}; - -/// Represents the params to set forking which can take various forms -/// - untagged -/// - tagged `forking` -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Forking { - pub json_rpc_url: Option, - pub block_number: Option, -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Forking { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(serde::Deserialize)] - #[serde(rename_all = "camelCase")] - struct ForkOpts { - pub json_rpc_url: Option, - #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] - pub block_number: Option, - } - - #[derive(serde::Deserialize)] - struct Tagged { - forking: ForkOpts, - } - #[derive(serde::Deserialize)] - #[serde(untagged)] - enum ForkingVariants { - Tagged(Tagged), - Fork(ForkOpts), - } - let f = match ForkingVariants::deserialize(deserializer)? { - ForkingVariants::Fork(ForkOpts { json_rpc_url, block_number }) => { - Self { json_rpc_url, block_number } - } - ForkingVariants::Tagged(f) => { - Self { json_rpc_url: f.forking.json_rpc_url, block_number: f.forking.block_number } - } - }; - Ok(f) - } -} - -/// Additional `evm_mine` options -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum EvmMineOptions { - Options { - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::num::u64_opt_via_ruint"))] - timestamp: Option, - // If `blocks` is given, it will mine exactly blocks number of blocks, regardless of any - // other blocks mined or reverted during it's operation - blocks: Option, - }, - /// The timestamp the block should be mined with - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::num::u64_opt_via_ruint"))] - Timestamp(Option), -} - -impl Default for EvmMineOptions { - fn default() -> Self { - Self::Options { timestamp: None, blocks: None } - } -} +use serde::Serializer; /// Represents the result of `eth_getWork` /// This may or may not include the block number @@ -96,145 +26,3 @@ impl serde::Serialize for Work { } } } - -/// A hex encoded or decimal index -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Index(usize); - -impl From for usize { - fn from(idx: Index) -> Self { - idx.0 - } -} - -#[cfg(feature = "serde")] -impl<'a> serde::Deserialize<'a> for Index { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'a>, - { - use std::fmt; - - struct IndexVisitor; - - impl<'a> serde::de::Visitor<'a> for IndexVisitor { - type Value = Index; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "hex-encoded or decimal index") - } - - fn visit_u64(self, value: u64) -> Result - where - E: Error, - { - Ok(Index(value as usize)) - } - - fn visit_str(self, value: &str) -> Result - where - E: Error, - { - if let Some(val) = value.strip_prefix("0x") { - usize::from_str_radix(val, 16).map(Index).map_err(|e| { - Error::custom(format!("Failed to parse hex encoded index value: {e}")) - }) - } else { - value - .parse::() - .map(Index) - .map_err(|e| Error::custom(format!("Failed to parse numeric index: {e}"))) - } - } - - fn visit_string(self, value: String) -> Result - where - E: Error, - { - self.visit_str(value.as_ref()) - } - } - - deserializer.deserialize_any(IndexVisitor) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct NodeInfo { - pub current_block_number: U64, - pub current_block_timestamp: u64, - pub current_block_hash: B256, - pub hard_fork: SpecId, - pub transaction_order: String, - pub environment: NodeEnvironment, - pub fork_config: NodeForkConfig, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct NodeEnvironment { - pub base_fee: u128, - pub chain_id: u64, - pub gas_limit: u128, - pub gas_price: u128, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct NodeForkConfig { - pub fork_url: Option, - pub fork_block_number: Option, - pub fork_retry_backoff: Option, -} - -/// Anvil equivalent of `hardhat_metadata`. -/// Metadata about the current Anvil instance. -/// See -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct AnvilMetadata { - pub client_version: &'static str, - pub chain_id: u64, - pub instance_id: B256, - pub latest_block_number: u64, - pub latest_block_hash: B256, - pub forked_network: Option, - pub snapshots: BTreeMap, -} - -/// Information about the forked network. -/// See -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ForkedNetwork { - pub chain_id: u64, - pub fork_block_number: u64, - pub fork_block_hash: TxHash, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serde_forking() { - let s = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com", - "blockNumber": "18441649" - } - }"#; - let f: Forking = serde_json::from_str(s).unwrap(); - assert_eq!( - f, - Forking { - json_rpc_url: Some("https://ethereumpublicnode.com".into()), - block_number: Some(18441649) - } - ); - } -} diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index ea97d24cd..dd15959b1 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -7,41 +7,39 @@ use std::str::FromStr; #[cfg_attr(feature = "clap", derive(clap::Parser), command(next_help_heading = "Server options"))] pub struct ServerConfig { /// The cors `allow_origin` header - #[cfg_attr( - feature = "clap", - arg( - long, - help = "Set the CORS allow_origin", - default_value = "*", - value_name = "ALLOW_ORIGIN" - ) - )] + #[cfg_attr(feature = "clap", arg(long, default_value = "*"))] pub allow_origin: HeaderValueWrapper, - /// Whether to enable CORS - #[cfg_attr( - feature = "clap", - arg(long, help = "Disable CORS", conflicts_with = "allow_origin") - )] + + /// Disable CORS. + #[cfg_attr(feature = "clap", arg(long, conflicts_with = "allow_origin"))] pub no_cors: bool, + + /// Disable the default request body size limit. At time of writing the default limit is 2MB. + #[cfg_attr(feature = "clap", arg(long))] + pub no_request_size_limit: bool, } impl ServerConfig { - /// Sets the "allow origin" header for cors + /// Sets the "allow origin" header for CORS. pub fn with_allow_origin(mut self, allow_origin: impl Into) -> Self { self.allow_origin = allow_origin.into(); self } - /// Whether to enable CORS + /// Whether to enable CORS. pub fn set_cors(mut self, cors: bool) -> Self { - self.no_cors = cors; + self.no_cors = !cors; self } } impl Default for ServerConfig { fn default() -> Self { - Self { allow_origin: "*".parse::().unwrap().into(), no_cors: false } + Self { + allow_origin: "*".parse::().unwrap().into(), + no_cors: false, + no_request_size_limit: false, + } } } diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index b60182505..075674667 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -12,6 +12,7 @@ use anvil_rpc::{ response::{ResponseResult, RpcResponse}, }; use axum::{ + extract::DefaultBodyLimit, http::{header, HeaderValue, Method}, routing::{post, MethodRouter}, Router, @@ -56,7 +57,7 @@ fn router_inner( root_method_router: MethodRouter, state: S, ) -> Router { - let ServerConfig { allow_origin, no_cors } = config; + let ServerConfig { allow_origin, no_cors, no_request_size_limit } = config; let mut router = Router::new() .route("/", root_method_router) @@ -72,6 +73,9 @@ fn router_inner( .allow_methods([Method::GET, Method::POST]), ); } + if no_request_size_limit { + router = router.layer(DefaultBodyLimit::disable()); + } router } diff --git a/crates/anvil/server/src/pubsub.rs b/crates/anvil/server/src/pubsub.rs index 2fe4358ef..8e5ac9b38 100644 --- a/crates/anvil/server/src/pubsub.rs +++ b/crates/anvil/server/src/pubsub.rs @@ -167,7 +167,7 @@ where let pin = self.get_mut(); loop { // drive the websocket - while let Poll::Ready(Ok(())) = pin.connection.poll_ready_unpin(cx) { + while matches!(pin.connection.poll_ready_unpin(cx), Poll::Ready(Ok(()))) { // only start sending if socket is ready if let Some(msg) = pin.pending.pop_front() { if let Err(err) = pin.connection.start_send_unpin(msg) { diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index ca86a06e1..f86703023 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,10 +1,10 @@ use crate::{ - config::DEFAULT_MNEMONIC, + config::{ForkChoice, DEFAULT_MNEMONIC}, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, AccountGenerator, Hardfork, NodeConfig, CHAIN_ID, }; use alloy_genesis::Genesis; -use alloy_primitives::{utils::Unit, U256}; +use alloy_primitives::{utils::Unit, B256, U256}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; @@ -204,10 +204,14 @@ impl NodeArgs { .with_genesis_balance(genesis_balance) .with_genesis_timestamp(self.timestamp) .with_port(self.port) - .with_fork_block_number( - self.evm_opts - .fork_block_number - .or_else(|| self.evm_opts.fork_url.as_ref().and_then(|f| f.block)), + .with_fork_choice( + match (self.evm_opts.fork_block_number, self.evm_opts.fork_transaction_hash) { + (Some(block), None) => Some(ForkChoice::Block(block)), + (None, Some(hash)) => Some(ForkChoice::Transaction(hash)), + _ => { + self.evm_opts.fork_url.as_ref().and_then(|f| f.block).map(ForkChoice::Block) + } + }, ) .with_fork_headers(self.evm_opts.fork_headers) .with_fork_chain_id(self.evm_opts.fork_chain_id.map(u64::from).map(U256::from)) @@ -226,6 +230,7 @@ impl NodeArgs { .with_transaction_order(self.order) .with_genesis(self.init) .with_steps_tracing(self.evm_opts.steps_tracing) + .with_print_logs(!self.evm_opts.disable_console_log) .with_auto_impersonate(self.evm_opts.auto_impersonate) .with_ipc(self.ipc) .with_code_size_limit(self.evm_opts.code_size_limit) @@ -394,6 +399,18 @@ pub struct AnvilEvmArgs { #[arg(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] pub fork_block_number: Option, + /// Fetch state from a specific transaction hash over a remote endpoint. + /// + /// See --fork-url. + #[arg( + long, + requires = "fork_url", + value_name = "TRANSACTION", + help_heading = "Fork config", + conflicts_with = "fork_block_number" + )] + pub fork_transaction_hash: Option, + /// Initial retry backoff on encountering errors. /// /// See --fork-url. @@ -491,6 +508,10 @@ pub struct AnvilEvmArgs { #[arg(long, visible_alias = "tracing")] pub steps_tracing: bool, + /// Disable printing of `console.log` invocations to stdout. + #[arg(long, visible_alias = "no-console-log")] + pub disable_console_log: bool, + /// Enable autoImpersonate on startup #[arg(long, visible_alias = "auto-impersonate")] pub auto_impersonate: bool, diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 097513847..ef6b2503d 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -9,16 +9,16 @@ use crate::{ time::duration_since_unix_epoch, }, fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, - pool::transactions::TransactionOrder, + pool::transactions::{PoolTransaction, TransactionOrder}, }, mem::{self, in_memory_db::MemDb}, FeeManager, Hardfork, PrecompileFactory, }; use alloy_genesis::Genesis; use alloy_network::AnyNetwork; -use alloy_primitives::{hex, utils::Unit, U256}; +use alloy_primitives::{hex, utils::Unit, BlockNumber, TxHash, U256}; use alloy_provider::Provider; -use alloy_rpc_types::BlockNumberOrTag; +use alloy_rpc_types::{BlockNumberOrTag, Transaction}; use alloy_signer::Signer; use alloy_signer_local::{ coins_bip39::{English, Mnemonic}, @@ -26,16 +26,19 @@ use alloy_signer_local::{ }; use alloy_transport::{Transport, TransportError}; use anvil_server::ServerConfig; +use eyre::Result; use foundry_common::{ - provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, + provider::{ProviderBuilder, RetryProvider}, + ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, }; use foundry_config::Config; use foundry_evm::{ + backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}, utils::apply_chain_and_block_specific_env_changes, }; +use itertools::Itertools; use parking_lot::RwLock; use rand::thread_rng; use revm::primitives::BlobExcessGasAndPrice; @@ -118,8 +121,8 @@ pub struct NodeConfig { pub silent: bool, /// url of the rpc server that should be used for any rpc calls pub eth_rpc_url: Option, - /// pins the block number for the state fork - pub fork_block_number: Option, + /// pins the block number or transaction hash for the state fork + pub fork_choice: Option, /// headers to use with `eth_rpc_url` pub fork_headers: Vec, /// specifies chain id for cache to skip fetching from remote in offline-start mode @@ -152,6 +155,8 @@ pub struct NodeConfig { pub ipc_path: Option>, /// Enable transaction/call steps tracing for debug calls returning geth-style traces pub enable_steps_tracing: bool, + /// Enable printing of `console.log` invocations. + pub print_logs: bool, /// Enable auto impersonation of accounts on startup pub enable_auto_impersonate: bool, /// Configure the code size limit @@ -242,6 +247,10 @@ Chain ID: {} fork.block_hash(), fork.chain_id() ); + + if let Some(tx_hash) = fork.transaction_hash() { + let _ = writeln!(config_string, "Transaction hash: {tx_hash}"); + } } else { let _ = write!( config_string, @@ -391,12 +400,13 @@ impl Default for NodeConfig { max_transactions: 1_000, silent: false, eth_rpc_url: None, - fork_block_number: None, + fork_choice: None, account_generator: None, base_fee: None, blob_excess_gas_and_price: None, enable_tracing: true, enable_steps_tracing: false, + print_logs: true, enable_auto_impersonate: false, no_storage_caching: false, server_config: Default::default(), @@ -694,10 +704,25 @@ impl NodeConfig { self } - /// Sets the `fork_block_number` to use to fork off from + /// Sets the `fork_choice` to use to fork off from based on a block number + #[must_use] + pub fn with_fork_block_number>(self, fork_block_number: Option) -> Self { + self.with_fork_choice(fork_block_number.map(Into::into)) + } + + /// Sets the `fork_choice` to use to fork off from based on a transaction hash + #[must_use] + pub fn with_fork_transaction_hash>( + self, + fork_transaction_hash: Option, + ) -> Self { + self.with_fork_choice(fork_transaction_hash.map(Into::into)) + } + + /// Sets the `fork_choice` to use to fork off from #[must_use] - pub fn with_fork_block_number>(mut self, fork_block_number: Option) -> Self { - self.fork_block_number = fork_block_number.map(Into::into); + pub fn with_fork_choice>(mut self, fork_choice: Option) -> Self { + self.fork_choice = fork_choice.map(Into::into); self } @@ -767,6 +792,13 @@ impl NodeConfig { self } + /// Sets whether to print `console.log` invocations to stdout. + #[must_use] + pub fn with_print_logs(mut self, print_logs: bool) -> Self { + self.print_logs = print_logs; + self + } + /// Sets whether to enable autoImpersonate #[must_use] pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { @@ -915,7 +947,6 @@ impl NodeConfig { timestamp: self.get_genesis_timestamp(), balance: self.genesis_balance, accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(), - fork_genesis_account_infos: Arc::new(Default::default()), genesis_init: self.genesis.clone(), }; @@ -927,6 +958,7 @@ impl NodeConfig { fees, Arc::new(RwLock::new(fork)), self.enable_steps_tracing, + self.print_logs, self.prune_history, self.transaction_block_keeper, self.block_time, @@ -987,19 +1019,23 @@ impl NodeConfig { let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) - .timeout_retry(self.fork_request_retries) + // .timeout_retry(self.fork_request_retries) .initial_backoff(self.fork_retry_backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) - .max_retry(10) + .max_retry(self.fork_request_retries) .initial_backoff(1000) .headers(self.fork_headers.clone()) .build() .expect("Failed to establish provider to fork url"), ); - let (fork_block_number, fork_chain_id) = if let Some(fork_block_number) = - self.fork_block_number + let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) = + &self.fork_choice { + let (fork_block_number, force_transactions) = + derive_block_and_transactions(fork_choice, &provider).await.expect( + "Failed to derive fork block number and force transactions from fork choice", + ); let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { @@ -1017,12 +1053,12 @@ impl NodeConfig { None }; - (fork_block_number, chain_id) + (fork_block_number, chain_id, force_transactions) } else { // pick the last block number but also ensure it's not pending anymore let bn = find_latest_fork_block(&provider).await.expect("Failed to get fork block number"); - (bn, None) + (bn, None, None) }; let block = provider @@ -1147,6 +1183,7 @@ latest block number: {latest_block}" eth_rpc_url, block_number: fork_block_number, block_hash, + transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()), provider, chain_id, override_chain_id, @@ -1159,6 +1196,7 @@ latest block number: {latest_block}" total_difficulty: block.header.total_difficulty.unwrap_or_default(), blob_gas_used: block.header.blob_gas_used, blob_excess_gas_and_price: env.block.blob_excess_gas_and_price.clone(), + force_transactions, }; let mut db = ForkedDatabase::new(backend, block_chain_db); @@ -1170,6 +1208,90 @@ latest block number: {latest_block}" } } +/// If the fork choice is a block number, simply return it with an empty list of transactions. +/// If the fork choice is a transaction hash, determine the block that the transaction was mined in, +/// and return the block number before the fork block along with all transactions in the fork block +/// that are before (and including) the fork transaction. +async fn derive_block_and_transactions( + fork_choice: &ForkChoice, + provider: &Arc, +) -> eyre::Result<(BlockNumber, Option>)> { + match fork_choice { + ForkChoice::Block(block_number) => Ok((block_number.to_owned(), None)), + ForkChoice::Transaction(transaction_hash) => { + // Determine the block that this transaction was mined in + let transaction = provider + .get_transaction_by_hash(transaction_hash.0.into()) + .await? + .ok_or(eyre::eyre!("Failed to get fork transaction by hash"))?; + let transaction_block_number = transaction.block_number.unwrap(); + + // Get the block pertaining to the fork transaction + let transaction_block = provider + .get_block_by_number(transaction_block_number.into(), true) + .await? + .ok_or(eyre::eyre!("Failed to get fork block by number"))?; + + // Filter out transactions that are after the fork transaction + let filtered_transactions: Vec<&Transaction> = transaction_block + .transactions + .as_transactions() + .ok_or(eyre::eyre!("Failed to get transactions from full fork block"))? + .iter() + .take_while_inclusive(|&transaction| transaction.hash != transaction_hash.0) + .collect(); + + // Convert the transactions to PoolTransactions + let force_transactions = filtered_transactions + .iter() + .map(|&transaction| PoolTransaction::try_from(transaction.clone())) + .collect::, _>>()?; + Ok((transaction_block_number.saturating_sub(1), Some(force_transactions))) + } + } +} + +/// Fork delimiter used to specify which block or transaction to fork from +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ForkChoice { + /// Block number to fork from + Block(BlockNumber), + /// Transaction hash to fork from + Transaction(TxHash), +} + +impl ForkChoice { + /// Returns the block number to fork from + pub fn block_number(&self) -> Option { + match self { + Self::Block(block_number) => Some(*block_number), + Self::Transaction(_) => None, + } + } + + /// Returns the transaction hash to fork from + pub fn transaction_hash(&self) -> Option { + match self { + Self::Block(_) => None, + Self::Transaction(transaction_hash) => Some(*transaction_hash), + } + } +} + +/// Convert a transaction hash into a ForkChoice +impl From for ForkChoice { + fn from(tx_hash: TxHash) -> Self { + Self::Transaction(tx_hash) + } +} + +/// Convert a decimal block number into a ForkChoice +impl From for ForkChoice { + fn from(block: u64) -> Self { + Self::Block(block) + } +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct PruneStateHistoryConfig { pub enabled: bool, @@ -1252,7 +1374,7 @@ impl AccountGenerator { let mut wallets = Vec::with_capacity(self.amount); for idx in 0..self.amount { let builder = - builder.clone().derivation_path(&format!("{derivation_path}{idx}")).unwrap(); + builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap(); let wallet = builder.build().unwrap().with_chain_id(Some(self.chain_id)); wallets.push(wallet) } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index e28a8c462..cd7485d5f 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -31,24 +31,29 @@ use crate::{ revm::primitives::{BlobExcessGasAndPrice, Output}, ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; -use alloy_consensus::{transaction::eip4844::TxEip4844Variant, TxEnvelope}; +use alloy_consensus::{transaction::eip4844::TxEip4844Variant, Account, TxEnvelope}; use alloy_dyn_abi::TypedData; use alloy_eips::eip2718::Encodable2718; use alloy_network::eip2718::Decodable2718; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, B64, U256, U64}; +use alloy_primitives::{Address, Bytes, Parity, TxHash, TxKind, B256, B64, U256, U64}; use alloy_rpc_types::{ + anvil::{ + ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, + }, request::TransactionRequest, state::StateOverride, + trace::{ + filter::TraceFilter, + geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace, + }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, AccessList, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag as BlockNumber, - BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Log, + BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Transaction, }; -use alloy_rpc_types_trace::{ - geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, -}; use alloy_serde::WithOtherFields; +use alloy_signer::Signature; use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ @@ -59,10 +64,7 @@ use anvil_core::{ }, EthRequest, }, - types::{ - AnvilMetadata, EvmMineOptions, ForkedNetwork, Forking, Index, NodeEnvironment, - NodeForkConfig, NodeInfo, Work, - }, + types::Work, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; use foundry_common::provider::ProviderBuilder; @@ -91,7 +93,7 @@ pub struct EthApi { pool: Arc, /// Holds all blockchain related data /// In-Memory only for now - pub(super) backend: Arc, + pub backend: Arc, /// Whether this node is mining is_mining: bool, /// available signers @@ -153,6 +155,9 @@ impl EthApi { match request { EthRequest::Web3ClientVersion(()) => self.client_version().to_rpc_result(), EthRequest::Web3Sha3(content) => self.sha3(content).to_rpc_result(), + EthRequest::EthGetAccount(addr, block) => { + self.get_account(addr, block).await.to_rpc_result() + } EthRequest::EthGetBalance(addr, block) => { self.balance(addr, block).await.to_rpc_result() } @@ -211,6 +216,9 @@ impl EthApi { self.get_proof(addr, keys, block).await.to_rpc_result() } EthRequest::EthSign(addr, content) => self.sign(addr, content).await.to_rpc_result(), + EthRequest::PersonalSign(content, addr) => { + self.sign(addr, content).await.to_rpc_result() + } EthRequest::EthSignTransaction(request) => { self.sign_transaction(*request).await.to_rpc_result() } @@ -288,6 +296,7 @@ impl EthApi { } EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), + EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), EthRequest::ImpersonateAccount(addr) => { self.anvil_impersonate_account(addr).await.to_rpc_result() } @@ -445,7 +454,7 @@ impl EthApi { alloy_primitives::Signature::from_scalars_and_parity( B256::with_last_byte(1), B256::with_last_byte(1), - false, + Parity::Parity(false), ) .unwrap(); return build_typed_transaction(request, nil_signature) @@ -652,6 +661,29 @@ impl EthApi { self.backend.get_balance(address, Some(block_request)).await } + /// Returns the ethereum account. + /// + /// Handler for ETH RPC call: `eth_getAccount` + pub async fn get_account( + &self, + address: Address, + block_number: Option, + ) -> Result { + node_info!("eth_getAccount"); + let block_request = self.block_request(block_number).await?; + + // check if the number predates the fork, if in fork mode + if let BlockRequest::Number(number) = block_request { + if let Some(fork) = self.get_fork() { + if fork.predates_fork(number) { + return Ok(fork.get_account(address, number).await?) + } + } + } + + self.backend.get_account_at_block(address, Some(block_request)).await + } + /// Returns content of the storage at given address. /// /// Handler for ETH RPC call: `eth_getStorageAt` @@ -934,7 +966,7 @@ impl EthApi { // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { - let bypass_signature = self.backend.cheats().bypass_signature(); + let bypass_signature = self.impersonated_signature(&request); let transaction = sign::build_typed_transaction(request, bypass_signature)?; self.ensure_typed_transaction_supported(&transaction)?; trace!(target : "node", ?from, "eth_sendTransaction: impersonating"); @@ -1520,8 +1552,8 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - opts: GethDefaultTracingOptions, - ) -> Result { + opts: GethDebugTracingCallOptions, + ) -> Result { node_info!("debug_traceCall"); let block_request = self.block_request(block_number).await?; let fees = FeeDetails::new( @@ -1532,7 +1564,9 @@ impl EthApi { )? .or_zero_fees(); - self.backend.call_with_tracing(request, fees, Some(block_request), opts).await + let result: std::result::Result = + self.backend.call_with_tracing(request, fees, Some(block_request), opts).await; + result } /// Returns traces for the transaction hash via parity's tracing endpoint @@ -1550,6 +1584,17 @@ impl EthApi { node_info!("trace_block"); self.backend.trace_block(block).await } + + /// Returns filtered traces over blocks + /// + /// Handler for RPC call: `trace_filter` + pub async fn trace_filter( + &self, + filter: TraceFilter, + ) -> Result> { + node_info!("trace_filter"); + self.backend.trace_filter(filter).await + } } // == impl EthApi anvil endpoints == @@ -1560,7 +1605,7 @@ impl EthApi { /// Handler for ETH RPC call: `anvil_impersonateAccount` pub async fn anvil_impersonate_account(&self, address: Address) -> Result<()> { node_info!("anvil_impersonateAccount"); - self.backend.impersonate(address).await?; + self.backend.impersonate(address); Ok(()) } @@ -1569,7 +1614,7 @@ impl EthApi { /// Handler for ETH RPC call: `anvil_stopImpersonatingAccount` pub async fn anvil_stop_impersonating_account(&self, address: Address) -> Result<()> { node_info!("anvil_stopImpersonatingAccount"); - self.backend.stop_impersonating(address).await?; + self.backend.stop_impersonating(address); Ok(()) } @@ -1578,7 +1623,7 @@ impl EthApi { /// Handler for ETH RPC call: `anvil_autoImpersonateAccount` pub async fn anvil_auto_impersonate_account(&self, enabled: bool) -> Result<()> { node_info!("anvil_autoImpersonateAccount"); - self.backend.auto_impersonate_account(enabled).await; + self.backend.auto_impersonate_account(enabled); Ok(()) } @@ -1812,21 +1857,22 @@ impl EthApi { let env = self.backend.env().read(); let fork_config = self.backend.get_fork(); let tx_order = self.transaction_order.read(); + let hard_fork: &str = env.handler_cfg.spec_id.into(); Ok(NodeInfo { - current_block_number: U64::from(self.backend.best_number()), + current_block_number: self.backend.best_number(), current_block_timestamp: env.block.timestamp.try_into().unwrap_or(u64::MAX), current_block_hash: self.backend.best_hash(), - hard_fork: env.handler_cfg.spec_id, + hard_fork: hard_fork.to_string(), transaction_order: match *tx_order { TransactionOrder::Fifo => "fifo".to_string(), TransactionOrder::Fees => "fees".to_string(), }, environment: NodeEnvironment { - base_fee: self.backend.base_fee(), + base_fee: U256::from(self.backend.base_fee()), chain_id: self.backend.chain_id().to::(), - gas_limit: self.backend.gas_limit(), - gas_price: self.gas_price(), + gas_limit: U256::from(self.backend.gas_limit()), + gas_price: U256::from(self.gas_price()), }, fork_config: fork_config .map(|fork| { @@ -1845,13 +1891,13 @@ impl EthApi { /// Retrieves metadata about the Anvil instance. /// /// Handler for RPC call: `anvil_metadata` - pub async fn anvil_metadata(&self) -> Result { + pub async fn anvil_metadata(&self) -> Result { node_info!("anvil_metadata"); let fork_config = self.backend.get_fork(); let snapshots = self.backend.list_snapshots(); - Ok(AnvilMetadata { - client_version: CLIENT_VERSION, + Ok(Metadata { + client_version: CLIENT_VERSION.to_string(), chain_id: self.backend.chain_id().to::(), latest_block_hash: self.backend.best_hash(), latest_block_number: self.backend.best_number(), @@ -1950,7 +1996,7 @@ impl EthApi { /// /// This will mine the blocks regardless of the configured mining mode. /// **Note**: ganache returns `0x0` here as placeholder for additional meta-data in the future. - pub async fn evm_mine(&self, opts: Option) -> Result { + pub async fn evm_mine(&self, opts: Option) -> Result { node_info!("evm_mine"); self.do_evm_mine(opts).await?; @@ -1967,7 +2013,7 @@ impl EthApi { /// **Note**: This behaves exactly as [Self::evm_mine] but returns different output, for /// compatibility reasons, this is a separate call since `evm_mine` is not an anvil original. /// and `ganache` may change the `0x0` placeholder. - pub async fn evm_mine_detailed(&self, opts: Option) -> Result> { + pub async fn evm_mine_detailed(&self, opts: Option) -> Result> { node_info!("evm_mine_detailed"); let mined_blocks = self.do_evm_mine(opts).await?; @@ -2078,7 +2124,7 @@ impl EthApi { let request = self.build_typed_tx_request(request, nonce)?; - let bypass_signature = self.backend.cheats().bypass_signature(); + let bypass_signature = self.impersonated_signature(&request); let transaction = sign::build_typed_transaction(request, bypass_signature)?; self.ensure_typed_transaction_supported(&transaction)?; @@ -2203,13 +2249,13 @@ impl EthApi { } /// Executes the `evm_mine` and returns the number of blocks mined - async fn do_evm_mine(&self, opts: Option) -> Result { + async fn do_evm_mine(&self, opts: Option) -> Result { let mut blocks_to_mine = 1u64; if let Some(opts) = opts { let timestamp = match opts { - EvmMineOptions::Timestamp(timestamp) => timestamp, - EvmMineOptions::Options { timestamp, blocks } => { + MineOptions::Timestamp(timestamp) => timestamp, + MineOptions::Options { timestamp, blocks } => { if let Some(blocks) = blocks { blocks_to_mine = blocks; } @@ -2575,6 +2621,28 @@ impl EthApi { self.backend.cheats().is_impersonated(addr) } + /// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC + fn impersonated_signature(&self, request: &TypedTransactionRequest) -> Signature { + match request { + // Only the legacy transaction type requires v to be in {27, 28}, thus + // requiring the use of Parity::NonEip155 + TypedTransactionRequest::Legacy(_) => Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + Parity::NonEip155(false), + ), + TypedTransactionRequest::EIP2930(_) | + TypedTransactionRequest::EIP1559(_) | + TypedTransactionRequest::EIP4844(_) | + TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + Parity::Parity(false), + ), + } + .unwrap() + } + /// Returns the nonce of the `address` depending on the `block_number` async fn get_transaction_count( &self, @@ -2640,6 +2708,7 @@ impl EthApi { TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(), TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(), TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(), + TypedTransaction::EIP7702(_) => self.backend.ensure_eip7702_active(), TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(), TypedTransaction::Legacy(_) => Ok(()), } @@ -2677,7 +2746,6 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result) -> u128 { match transaction_request_to_typed(request.clone()) { Some(request) => match request { @@ -2738,6 +2806,8 @@ impl TryFrom, u128, State)>> for GasEs InstructionResult::OpcodeNotFound | InstructionResult::CallNotAllowedInsideStatic | InstructionResult::StateChangeDuringStaticCall | + InstructionResult::InvalidExtDelegateCallTarget | + InstructionResult::InvalidEXTCALLTarget | InstructionResult::InvalidFEOpcode | InstructionResult::InvalidJump | InstructionResult::NotActivated | @@ -2754,10 +2824,15 @@ impl TryFrom, u128, State)>> for GasEs InstructionResult::FatalExternalError | InstructionResult::OutOfFunds | InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), + // Handle Revm EOF InstructionResults: Not supported yet InstructionResult::ReturnContractInNotInitEOF | InstructionResult::EOFOpcodeDisabledInLegacy | - InstructionResult::EOFFunctionStackOverflow => Ok(Self::EvmError(exit)), + InstructionResult::EOFFunctionStackOverflow | + InstructionResult::CreateInitCodeStartingEF00 | + InstructionResult::InvalidEOFInitCode | + InstructionResult::EofAuxDataOverflow | + InstructionResult::EofAuxDataTooSmall => Ok(Self::EvmError(exit)), }, } } diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index dbc58670f..5b498f963 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -1,7 +1,6 @@ //! Support for "cheat codes" / bypass functions -use alloy_primitives::{Address, Signature}; -use anvil_core::eth::transaction::impersonated_signature; +use alloy_primitives::Address; use parking_lot::RwLock; use std::{collections::HashSet, sync::Arc}; @@ -48,11 +47,6 @@ impl CheatsManager { } } - /// Returns the signature to use to bypass transaction signing - pub fn bypass_signature(&self) -> Signature { - self.state.read().bypass_signature - } - /// Sets the auto impersonation flag which if set to true will make the `is_impersonated` /// function always return true pub fn set_auto_impersonate_account(&self, enabled: bool) { @@ -67,22 +61,10 @@ impl CheatsManager { } /// Container type for all the state variables -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct CheatsState { /// All accounts that are currently impersonated pub impersonated_accounts: HashSet
, - /// The signature used for the `eth_sendUnsignedTransaction` cheat code - pub bypass_signature: Signature, /// If set to true will make the `is_impersonated` function always return true pub auto_impersonate_accounts: bool, } - -impl Default for CheatsState { - fn default() -> Self { - Self { - impersonated_accounts: Default::default(), - bypass_signature: impersonated_signature(), - auto_impersonate_accounts: false, - } - } -} diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 9182cdadb..e6d9541f3 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,14 +1,18 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::revm::primitives::AccountInfo; +use crate::{mem::storage::MinedTransaction, revm::primitives::AccountInfo}; use alloy_consensus::Header; use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64}; use alloy_rpc_types::BlockId; -use anvil_core::eth::{block::Block, transaction::TypedTransaction}; +use anvil_core::eth::{ + block::Block, + transaction::{MaybeImpersonatedTransaction, TransactionInfo, TypedReceipt}, +}; use foundry_common::errors::FsPathError; use foundry_evm::{ - backend::{DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot}, - fork::BlockchainDb, + backend::{ + BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot, + }, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, primitives::{BlockEnv, Bytecode, HashMap, KECCAK_EMPTY}, @@ -119,6 +123,7 @@ pub trait Db: at: BlockEnv, best_number: U64, blocks: Vec, + transactions: Vec, ) -> DatabaseResult>; /// Deserialize and add all chain data to the backend storage @@ -192,6 +197,7 @@ impl + Send + Sync + Clone + fmt::Debug> D _at: BlockEnv, _best_number: U64, _blocks: Vec, + _transaction: Vec, ) -> DatabaseResult> { Ok(None) } @@ -290,7 +296,7 @@ impl DatabaseRef for StateDb { self.0.storage_ref(address, index) } - fn block_hash_ref(&self, number: U256) -> DatabaseResult { + fn block_hash_ref(&self, number: u64) -> DatabaseResult { self.0.block_hash_ref(number) } } @@ -324,6 +330,8 @@ pub struct SerializableState { pub best_block_number: Option, #[serde(default)] pub blocks: Vec, + #[serde(default)] + pub transactions: Vec, } impl SerializableState { @@ -354,7 +362,7 @@ pub struct SerializableAccountRecord { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableBlock { pub header: Header, - pub transactions: Vec, + pub transactions: Vec, pub ommers: Vec
, } @@ -377,3 +385,33 @@ impl From for Block { } } } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SerializableTransaction { + pub info: TransactionInfo, + pub receipt: TypedReceipt, + pub block_hash: B256, + pub block_number: u64, +} + +impl From for SerializableTransaction { + fn from(transaction: MinedTransaction) -> Self { + Self { + info: transaction.info, + receipt: transaction.receipt, + block_hash: transaction.block_hash, + block_number: transaction.block_number, + } + } +} + +impl From for MinedTransaction { + fn from(transaction: SerializableTransaction) -> Self { + Self { + info: transaction.info, + receipt: transaction.receipt, + block_hash: transaction.block_hash, + block_number: transaction.block_number, + } + } +} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 163428eeb..d56db9069 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -66,6 +66,7 @@ impl ExecutedTransaction { TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt { inner: receipt_with_bloom, deposit_nonce: Some(tx.nonce), @@ -104,6 +105,7 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator> /// Cumulative blob gas used by all executed transactions pub blob_gas_used: u128, pub enable_steps_tracing: bool, + pub print_logs: bool, /// Precompiles to inject to the EVM. pub precompile_factory: Option>, } @@ -304,6 +306,9 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } + if self.print_logs { + inspector = inspector.with_log_collector(); + } let exec_result = { let mut evm = diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 25e001da0..d4b51ae96 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,19 +1,20 @@ //! Support for forking off another client -use crate::eth::{backend::db::Db, error::BlockchainError}; +use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; +use alloy_consensus::Account; use alloy_primitives::{Address, Bytes, StorageValue, B256, U256}; use alloy_provider::{ ext::{DebugApi, TraceApi}, Provider, }; use alloy_rpc_types::{ - request::TransactionRequest, AccessListWithGasUsed, Block, BlockId, - BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, - Filter, Log, Transaction, -}; -use alloy_rpc_types_trace::{ - geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace as Trace, + request::TransactionRequest, + trace::{ + geth::{GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace as Trace, + }, + AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + EIP1186AccountProofResponse, FeeHistory, Filter, Log, Transaction, }; use alloy_serde::WithOtherFields; use alloy_transport::TransportError; @@ -118,6 +119,11 @@ impl ClientFork { self.config.read().block_number } + /// Returns the transaction hash we forked off of, if any. + pub fn transaction_hash(&self) -> Option { + self.config.read().transaction_hash + } + pub fn total_difficulty(&self) -> U256 { self.config.read().total_difficulty } @@ -261,6 +267,15 @@ impl ClientFork { self.provider().get_transaction_count(address).block_id(block.into()).await } + pub async fn get_account( + &self, + address: Address, + blocknumber: u64, + ) -> Result { + trace!(target: "backend::fork", "get_account={:?}", address); + self.provider().get_account(address).await.block_id(blocknumber.into()).await + } + pub async fn transaction_by_block_number_and_index( &self, number: u64, @@ -579,6 +594,8 @@ pub struct ClientForkConfig { pub block_number: u64, /// The hash of the forked block pub block_hash: B256, + /// The transaction hash we forked off of, if any. + pub transaction_hash: Option, // TODO make provider agnostic pub provider: Arc, pub chain_id: u64, @@ -601,6 +618,8 @@ pub struct ClientForkConfig { pub compute_units_per_second: u64, /// total difficulty of the chain until this block pub total_difficulty: U256, + /// Transactions to force include in the forked chain + pub force_transactions: Option>, } impl ClientForkConfig { @@ -614,8 +633,8 @@ impl ClientForkConfig { self.provider = Arc::new( ProviderBuilder::new(url.as_str()) .timeout(self.timeout) - .timeout_retry(self.retries) - .max_retry(10) + // .timeout_retry(self.retries) + .max_retry(self.retries) .initial_backoff(self.backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .build() diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index ebe6e6f6e..ee459204d 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -1,17 +1,12 @@ //! Genesis settings -use crate::eth::backend::db::{Db, MaybeFullDatabase}; +use crate::eth::backend::db::Db; use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, U256}; use foundry_evm::{ - backend::{DatabaseError, DatabaseResult, StateSnapshot}, - revm::{ - db::DatabaseRef, - primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, - }, + backend::DatabaseResult, + revm::primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, }; -use parking_lot::Mutex; -use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLockWriteGuard; /// Genesis settings @@ -23,11 +18,6 @@ pub struct GenesisConfig { pub balance: U256, /// All accounts that should be initialised at genesis pub accounts: Vec
, - /// The account object stored in the [`revm::Database`] - /// - /// We store this for forking mode so we can cheaply reset the dev accounts and don't - /// need to fetch them again. - pub fork_genesis_account_infos: Arc>>, /// The `genesis.json` if provided pub genesis_init: Option, } @@ -77,75 +67,4 @@ impl GenesisConfig { code, } } - - /// Returns a database wrapper that points to the genesis and is aware of all provided - /// [AccountInfo] - pub(crate) fn state_db_at_genesis<'a>( - &self, - db: Box, - ) -> AtGenesisStateDb<'a> { - AtGenesisStateDb { - genesis: self.genesis_init.clone(), - accounts: self.account_infos().collect(), - db, - } - } -} - -/// A Database implementation that is at the genesis state. -/// -/// This is only used in forking mode where we either need to fetch the state from remote if the -/// account was not provided via custom genesis, which would override anything available from remote -/// starting at the genesis, Note: "genesis" in the context of the Backend means, the block the -/// backend was created, which is `0` in normal mode and `fork block` in forking mode. -pub(crate) struct AtGenesisStateDb<'a> { - genesis: Option, - accounts: HashMap, - db: Box, -} - -impl<'a> DatabaseRef for AtGenesisStateDb<'a> { - type Error = DatabaseError; - fn basic_ref(&self, address: Address) -> DatabaseResult> { - if let Some(acc) = self.accounts.get(&(address)).cloned() { - return Ok(Some(acc)) - } - self.db.basic_ref(address) - } - - fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult { - if let Some((_, acc)) = self.accounts.iter().find(|(_, acc)| acc.code_hash == code_hash) { - return Ok(acc.code.clone().unwrap_or_default()) - } - self.db.code_by_hash_ref(code_hash) - } - - fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult { - if let Some(acc) = self.genesis.as_ref().and_then(|genesis| genesis.alloc.get(&(address))) { - if let Some(storage) = acc.storage.as_ref() { - return Ok(U256::from_be_bytes( - storage.get(&B256::from(index)).copied().unwrap_or_default().0, - )) - } - } - self.db.storage_ref(address, index) - } - - fn block_hash_ref(&self, number: U256) -> DatabaseResult { - self.db.block_hash_ref(number) - } -} - -impl<'a> MaybeFullDatabase for AtGenesisStateDb<'a> { - fn clear_into_snapshot(&mut self) -> StateSnapshot { - self.db.clear_into_snapshot() - } - - fn clear(&mut self) { - self.db.clear() - } - - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { - self.db.init_from_snapshot(snapshot) - } } diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index ae325f975..a179f50c3 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -1,15 +1,15 @@ use crate::{ eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, - SerializableState, StateDb, + SerializableState, SerializableTransaction, StateDb, }, revm::primitives::AccountInfo, }; use alloy_primitives::{Address, B256, U256, U64}; use alloy_rpc_types::BlockId; use foundry_evm::{ - backend::{DatabaseResult, RevertSnapshotAction, StateSnapshot}, - fork::{database::ForkDbSnapshot, BlockchainDb}, + backend::{BlockchainDb, DatabaseResult, RevertSnapshotAction, StateSnapshot}, + fork::database::ForkDbSnapshot, revm::Database, }; @@ -37,6 +37,7 @@ impl Db for ForkedDatabase { at: BlockEnv, best_number: U64, blocks: Vec, + transactions: Vec, ) -> DatabaseResult> { let mut db = self.database().clone(); let accounts = self @@ -66,6 +67,7 @@ impl Db for ForkedDatabase { accounts, best_block_number: Some(best_number), blocks, + transactions, })) } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 6269727c1..059c00f32 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -3,7 +3,7 @@ use crate::{ eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, - SerializableState, StateDb, + SerializableState, SerializableTransaction, StateDb, }, mem::state::state_root, revm::{db::DbAccount, primitives::AccountInfo}, @@ -11,8 +11,7 @@ use crate::{ use alloy_primitives::{Address, B256, U256, U64}; use alloy_rpc_types::BlockId; use foundry_evm::{ - backend::{DatabaseResult, StateSnapshot}, - fork::BlockchainDb, + backend::{BlockchainDb, DatabaseResult, StateSnapshot}, hashbrown::HashMap, }; @@ -38,6 +37,7 @@ impl Db for MemDb { at: BlockEnv, best_number: U64, blocks: Vec, + transactions: Vec, ) -> DatabaseResult> { let accounts = self .inner @@ -67,6 +67,7 @@ impl Db for MemDb { accounts, best_block_number: Some(best_number), blocks, + transactions, })) } @@ -161,7 +162,10 @@ mod tests { dump_db.set_storage_at(test_addr, U256::from(1234567), U256::from(1)).unwrap(); // blocks dumping/loading tested in storage.rs - let state = dump_db.dump_state(Default::default(), U64::ZERO, Vec::new()).unwrap().unwrap(); + let state = dump_db + .dump_state(Default::default(), U64::ZERO, Vec::new(), Vec::new()) + .unwrap() + .unwrap(); let mut load_db = MemDb::default(); diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 0fe0f26af..68336935f 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,13 +1,15 @@ //! Anvil specific [`revm::Inspector`] implementation -use crate::{eth::macros::node_info, revm::Database}; +use crate::revm::Database; use alloy_primitives::{Address, Log}; use foundry_evm::{ call_inspectors, decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, revm::{ - interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter}, + interpreter::{ + CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter, + }, primitives::U256, EvmContext, }, @@ -20,7 +22,7 @@ use foundry_evm::{ pub struct Inspector { pub tracer: Option, /// collects all `console.sol` logs - pub log_collector: LogCollector, + pub log_collector: Option, } impl Inspector { @@ -28,7 +30,9 @@ impl Inspector { /// /// This will log all `console.sol` logs pub fn print_logs(&self) { - print_logs(&self.log_collector.logs) + if let Some(collector) = &self.log_collector { + print_logs(&collector.logs); + } } /// Configures the `Tracer` [`revm::Inspector`] @@ -37,54 +41,58 @@ impl Inspector { self } + pub fn with_config(mut self, config: TracingInspectorConfig) -> Self { + self.tracer = Some(TracingInspector::new(config)); + self + } + /// Enables steps recording for `Tracer`. pub fn with_steps_tracing(mut self) -> Self { self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all())); self } + + /// Configures the `Tracer` [`revm::Inspector`] + pub fn with_log_collector(mut self) -> Self { + self.log_collector = Some(Default::default()); + self + } } impl revm::Inspector for Inspector { - #[inline] fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { inspector.initialize_interp(interp, ecx); }); } - #[inline] fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step(interp, ecx); }); } - #[inline] fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step_end(interp, ecx); }); } - #[inline] - fn log(&mut self, ecx: &mut EvmContext, log: &Log) { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - inspector.log(ecx, log); + fn log(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext, log: &Log) { + call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { + inspector.log(interp, ecx, log); }); } - #[inline] fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - if let Some(outcome) = inspector.call(ecx, inputs) { - return Some(outcome); - } - }); - + call_inspectors!( + #[ret] + [&mut self.tracer, &mut self.log_collector], + |inspector| inspector.call(ecx, inputs).map(Some), + ); None } - #[inline] fn call_end( &mut self, ecx: &mut EvmContext, @@ -98,7 +106,6 @@ impl revm::Inspector for Inspector { outcome } - #[inline] fn create( &mut self, ecx: &mut EvmContext, @@ -112,7 +119,6 @@ impl revm::Inspector for Inspector { None } - #[inline] fn create_end( &mut self, ecx: &mut EvmContext, @@ -126,6 +132,34 @@ impl revm::Inspector for Inspector { outcome } + #[inline] + fn eofcreate( + &mut self, + ecx: &mut EvmContext, + inputs: &mut EOFCreateInputs, + ) -> Option { + if let Some(tracer) = &mut self.tracer { + if let Some(out) = tracer.eofcreate(ecx, inputs) { + return Some(out); + } + } + None + } + + #[inline] + fn eofcreate_end( + &mut self, + ecx: &mut EvmContext, + inputs: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.eofcreate_end(ecx, inputs, outcome); + } + + outcome + } + #[inline] fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { if let Some(tracer) = &mut self.tracer { @@ -137,9 +171,8 @@ impl revm::Inspector for Inspector { impl InspectorExt for Inspector {} /// Prints all the logs -#[inline] pub fn print_logs(logs: &[Log]) { for log in decode_console_logs(logs) { - node_info!("{}", log); + tracing::info!(target: crate::logging::EVM_CONSOLE_LOG_TARGET, "{}", log); } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index bb3de1b09..0456b4a4c 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -32,33 +32,41 @@ use crate::{ revm::{db::DatabaseRef, primitives::AccountInfo}, NodeConfig, PrecompileFactory, }; -use alloy_consensus::{Header, Receipt, ReceiptWithBloom}; +use alloy_consensus::{Account, Header, Receipt, ReceiptWithBloom}; use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256, U64}; use alloy_rpc_types::{ - request::TransactionRequest, serde_helpers::JsonStorageKey, state::StateOverride, AccessList, - Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, + anvil::Forking, + request::TransactionRequest, + serde_helpers::JsonStorageKey, + state::StateOverride, + trace::{ + filter::TraceFilter, + geth::{ + GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, GethTrace, NoopFrame, + }, + parity::{ + Action::{Call, Create, Reward, Selfdestruct}, + LocalizedTransactionTrace, + }, + }, + AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, - FilteredParams, Header as AlloyHeader, Log, Transaction, TransactionReceipt, -}; -use alloy_rpc_types_trace::{ - geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, + FilteredParams, Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, }; use alloy_serde::WithOtherFields; use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; -use anvil_core::{ - eth::{ - block::{Block, BlockInfo}, - transaction::{ - DepositReceipt, MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, - TransactionInfo, TypedReceipt, TypedTransaction, - }, - utils::meets_eip155, +use anvil_core::eth::{ + block::{Block, BlockInfo}, + transaction::{ + DepositReceipt, MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, + TransactionInfo, TypedReceipt, TypedTransaction, }, - types::{Forking, Index}, + utils::meets_eip155, }; use anvil_rpc::error::RpcError; + use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertSnapshotAction}, @@ -70,9 +78,10 @@ use foundry_evm::{ interpreter::InstructionResult, primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, Output, SpecId, - TransactTo, TxEnv, KECCAK_EMPTY, + TxEnv, KECCAK_EMPTY, }, }, + traces::TracingInspectorConfig, utils::new_evm_with_inspector_ref, InspectorExt, }; @@ -166,6 +175,7 @@ pub struct Backend { /// keeps track of active snapshots at a specific block active_snapshots: Arc>>, enable_steps_tracing: bool, + print_logs: bool, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory @@ -187,6 +197,7 @@ impl Backend { fees: FeeManager, fork: Arc>>, enable_steps_tracing: bool, + print_logs: bool, prune_state_history_config: PruneStateHistoryConfig, transaction_block_keeper: Option, automine_block_time: Option, @@ -239,6 +250,7 @@ impl Backend { genesis, active_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, + print_logs, prune_state_history_config, transaction_block_keeper, node_config, @@ -270,7 +282,7 @@ impl Backend { /// Applies the configured genesis settings /// /// This will fund, create the genesis accounts - async fn apply_genesis(&self) -> DatabaseResult<()> { + async fn apply_genesis(&self) -> Result<(), DatabaseError> { trace!(target: "backend", "setting genesis balances"); if self.fork.read().is_some() { @@ -292,19 +304,10 @@ impl Backend { let mut db = self.db.write().await; - // in fork mode we only set the balance, this way the accountinfo is fetched from the - // remote client, preserving code and nonce. The reason for that is private keys for dev - // accounts are commonly known and are used on testnets - let mut fork_genesis_infos = self.genesis.fork_genesis_account_infos.lock(); - fork_genesis_infos.clear(); - for res in genesis_accounts { - let (address, mut info) = res.map_err(DatabaseError::display)??; + let (address, mut info) = res.unwrap()?; info.balance = self.genesis.balance; db.insert_account(address, info.clone()); - - // store the fetched AccountInfo, so we can cheaply reset in [Self::reset_fork()] - fork_genesis_infos.push(info); } } else { let mut db = self.db.write().await; @@ -326,26 +329,25 @@ impl Backend { /// Sets the account to impersonate /// /// Returns `true` if the account is already impersonated - pub async fn impersonate(&self, addr: Address) -> DatabaseResult { + pub fn impersonate(&self, addr: Address) -> bool { if self.cheats.impersonated_accounts().contains(&addr) { - return Ok(true); + return true } // Ensure EIP-3607 is disabled let mut env = self.env.write(); env.cfg.disable_eip3607 = true; - Ok(self.cheats.impersonate(addr)) + self.cheats.impersonate(addr) } /// Removes the account that from the impersonated set /// /// If the impersonated `addr` is a contract then we also reset the code here - pub async fn stop_impersonating(&self, addr: Address) -> DatabaseResult<()> { + pub fn stop_impersonating(&self, addr: Address) { self.cheats.stop_impersonating(&addr); - Ok(()) } /// If set to true will make every account impersonated - pub async fn auto_impersonate_account(&self, enabled: bool) { + pub fn auto_impersonate_account(&self, enabled: bool) { self.cheats.set_auto_impersonate_account(enabled); } @@ -453,23 +455,9 @@ impl Backend { fork.total_difficulty(), ); self.states.write().clear(); + self.db.write().await.clear(); - // insert back all genesis accounts, by reusing cached `AccountInfo`s we don't need to - // fetch the data via RPC again - let mut db = self.db.write().await; - - // clear database - db.clear(); - - let fork_genesis_infos = self.genesis.fork_genesis_account_infos.lock(); - for (address, info) in - self.genesis.accounts.iter().copied().zip(fork_genesis_infos.iter().cloned()) - { - db.insert_account(address, info); - } - - // reset the genesis.json alloc - self.genesis.apply_genesis_json_alloc(db)?; + self.apply_genesis().await?; Ok(()) } else { @@ -592,6 +580,11 @@ impl Backend { (self.spec_id() as u8) >= (SpecId::CANCUN as u8) } + /// Returns true for post Prague + pub fn is_eip7702(&self) -> bool { + (self.spec_id() as u8) >= (SpecId::PRAGUE as u8) + } + /// Returns true if op-stack deposits are active pub fn is_optimism(&self) -> bool { self.env.read().handler_cfg.is_optimism @@ -620,6 +613,13 @@ impl Backend { Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork) } + pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> { + if self.is_eip7702() { + return Ok(()); + } + Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork) + } + /// Returns an error if op-stack deposits are not active pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { @@ -738,7 +738,8 @@ impl Backend { let at = self.env.read().block.clone(); let best_number = self.blockchain.storage.read().best_number; let blocks = self.blockchain.storage.read().serialized_blocks(); - let state = self.db.read().await.dump_state(at, best_number, blocks)?; + let transactions = self.blockchain.storage.read().serialized_transactions(); + let state = self.db.read().await.dump_state(at, best_number, blocks, transactions)?; state.ok_or_else(|| { RpcError::invalid_params("Dumping state not supported with the current configuration") .into() @@ -775,6 +776,7 @@ impl Backend { } self.blockchain.storage.write().load_blocks(state.blocks.clone()); + self.blockchain.storage.write().load_transactions(state.transactions.clone()); Ok(true) } @@ -898,6 +900,7 @@ impl Backend { gas_used: 0, blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + print_logs: self.print_logs, precompile_factory: self.precompile_factory.clone(), }; @@ -954,6 +957,12 @@ impl Backend { let (executed_tx, block_hash) = { let mut db = self.db.write().await; + + // finally set the next block timestamp, this is done just before execution, because + // there can be concurrent requests that can delay acquiring the db lock and we want + // to ensure the timestamp is as close as possible to the actual execution. + env.block.timestamp = U256::from(self.time.next_timestamp()); + let executor = TransactionExecutor { db: &mut *db, validator: self, @@ -964,6 +973,7 @@ impl Backend { gas_used: 0, blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + print_logs: self.print_logs, precompile_factory: self.precompile_factory.clone(), }; let executed_tx = executor.execute(); @@ -1159,19 +1169,20 @@ impl Backend { gas_priority_fee: max_priority_fee_per_gas.map(U256::from), max_fee_per_blob_gas: max_fee_per_blob_gas.map(U256::from), transact_to: match to { - Some(addr) => TransactTo::Call(*addr), - None => TransactTo::Create, + Some(addr) => TxKind::Call(*addr), + None => TxKind::Create, }, value: value.unwrap_or_default(), data: input.into_input().unwrap_or_default(), chain_id: None, nonce, - access_list: access_list.unwrap_or_default().flattened(), + access_list: access_list.unwrap_or_default().into(), blob_hashes: blob_versioned_hashes.unwrap_or_default(), optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, + authorization_list: None, }; - if env.block.basefee == revm::primitives::U256::ZERO { + if env.block.basefee.is_zero() { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set env.cfg.disable_base_fee = true; @@ -1214,12 +1225,58 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_request: Option, - opts: GethDefaultTracingOptions, - ) -> Result { + opts: GethDebugTracingCallOptions, + ) -> Result { + let GethDebugTracingCallOptions { tracing_options, block_overrides: _, state_overrides: _ } = + opts; + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; + self.with_database_at(block_request, |state, block| { - let mut inspector = Inspector::default().with_steps_tracing(); let block_number = block.number; + if let Some(tracer) = tracer { + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::CallTracer => { + let call_config = tracer_config + .into_call_config() + .map_err(|e| (RpcError::invalid_params(e.to_string())))?; + + let mut inspector = Inspector::default().with_config( + TracingInspectorConfig::from_geth_call_config(&call_config), + ); + + let env = self.build_call_env(request, fee_details, block); + let mut evm = + self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + + drop(evm); + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); + + Ok(tracing_inspector + .into_geth_builder() + .geth_call_traces(call_config, result.gas_used()) + .into()) + } + GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), + GethDebugBuiltInTracerType::FourByteTracer | + GethDebugBuiltInTracerType::PreStateTracer | + GethDebugBuiltInTracerType::MuxTracer => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + }, + + GethDebugTracerType::JsTracer(_code) => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + } + } + + // defaults to StructLog tracer used since no tracer is specified + let mut inspector = + Inspector::default().with_config(TracingInspectorConfig::from_geth_config(&config)); + let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); let ResultAndState { result, state: _ } = evm.transact()?; @@ -1235,10 +1292,16 @@ impl Backend { }; drop(evm); - let tracer = inspector.tracer.expect("tracer disappeared"); + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); - let res = tracer.into_geth_builder().geth_traces(gas_used, return_value, opts); + trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); + + let res = tracing_inspector + .into_geth_builder() + .geth_traces(gas_used, return_value, config) + .into(); + Ok(res) }) .await? @@ -1385,7 +1448,7 @@ impl Backend { to_on_fork = fork.block_number(); } - if fork.predates_fork(from) { + if fork.predates_fork_inclusive(from) { // this data is only available on the forked client let filter = filter.clone().from_block(from).to_block(to_on_fork); all_logs = fork.logs(&filter).await?; @@ -1723,19 +1786,18 @@ impl Backend { let block_number: U256 = U256::from(self.convert_block_number(block_number)); if block_number < self.env.read().block.number { + if let Some((block_hash, block)) = self + .block_by_number(BlockNumber::Number(block_number.to::())) + .await? + .and_then(|block| Some((block.header.hash?, block))) { - let mut states = self.states.write(); - - if let Some((state, block)) = self - .get_block(block_number.to::()) - .and_then(|block| Some((states.get(&block.header.hash_slow())?, block))) - { + if let Some(state) = self.states.write().get(&block_hash) { let block = BlockEnv { - number: U256::from(block.header.number), - coinbase: block.header.beneficiary, + number: block_number, + coinbase: block.header.miner, timestamp: U256::from(block.header.timestamp), difficulty: block.header.difficulty, - prevrandao: Some(block.header.mix_hash), + prevrandao: block.header.mix_hash, basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), gas_limit: U256::from(block.header.gas_limit), ..Default::default() @@ -1744,25 +1806,6 @@ impl Backend { } } - // there's an edge case in forking mode if the requested `block_number` is __exactly__ - // the forked block, which should be fetched from remote but since we allow genesis - // accounts this may not be accurate data because an account could be provided via - // genesis - // So this provides calls the given provided function `f` with a genesis aware database - if let Some(fork) = self.get_fork() { - if block_number == U256::from(fork.block_number()) { - let mut block = self.env.read().block.clone(); - let db = self.db.read().await; - let gen_db = self.genesis.state_db_at_genesis(Box::new(&*db)); - - block.number = block_number; - block.timestamp = U256::from(fork.timestamp()); - block.basefee = U256::from(fork.base_fee().unwrap_or_default()); - - return Ok(f(Box::new(&gen_db), block)); - } - } - warn!(target: "backend", "Not historic state found for block={}", block_number); return Err(BlockchainError::BlockOutOfRange( self.env.read().block.number.to::(), @@ -1835,6 +1878,23 @@ impl Backend { .await? } + pub async fn get_account_at_block( + &self, + address: Address, + block_request: Option, + ) -> Result { + self.with_database_at(block_request, |block_db, _| { + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); + let storage_root = storage_root(&account.storage); + let code_hash = account.info.code_hash; + let balance = account.info.balance; + let nonce = account.info.nonce; + Ok(Account { balance, nonce, code_hash, storage_root }) + }) + .await? + } + pub fn get_balance_with_state( &self, state: D, @@ -1921,8 +1981,8 @@ impl Backend { hash: B256, opts: GethDebugTracingOptions, ) -> Result { - if let Some(traces) = self.mined_geth_trace_transaction(hash, opts.clone()) { - return Ok(GethTrace::Default(traces)); + if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()) { + return trace; } if let Some(fork) = self.get_fork() { @@ -1936,8 +1996,8 @@ impl Backend { &self, hash: B256, opts: GethDebugTracingOptions, - ) -> Option { - self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts.config)) + ) -> Option> { + self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts)) } /// Returns the traces for the given block @@ -1981,6 +2041,61 @@ impl Backend { Ok(None) } + // Returns the traces matching a given filter + pub async fn trace_filter( + &self, + filter: TraceFilter, + ) -> Result, BlockchainError> { + let matcher = filter.matcher(); + let start = filter.from_block.unwrap_or(0); + let end = filter.to_block.unwrap_or(self.best_number()); + + let dist = end.saturating_sub(start); + if dist == 0 { + return Err(BlockchainError::RpcError(RpcError::invalid_params( + "invalid block range, ensure that to block is greater than from block".to_string(), + ))); + } + if dist > 300 { + return Err(BlockchainError::RpcError(RpcError::invalid_params( + "block range too large, currently limited to 300".to_string(), + ))); + } + + // Accumulate tasks for block range + let mut trace_tasks = vec![]; + for num in start..=end { + trace_tasks.push(self.trace_block(num.into())); + } + + // Execute tasks and filter traces + let traces = futures::future::try_join_all(trace_tasks).await?; + let filtered_traces = + traces.into_iter().flatten().filter(|trace| match &trace.trace.action { + Call(call) => matcher.matches(call.from, Some(call.to)), + Create(create) => matcher.matches(create.from, None), + Selfdestruct(self_destruct) => { + matcher.matches(self_destruct.address, Some(self_destruct.refund_address)) + } + Reward(reward) => matcher.matches(reward.author, None), + }); + + // Apply after and count + let filtered_traces: Vec<_> = if let Some(after) = filter.after { + filtered_traces.skip(after as usize).collect() + } else { + filtered_traces.collect() + }; + + let filtered_traces: Vec<_> = if let Some(count) = filter.count { + filtered_traces.into_iter().take(count as usize).collect() + } else { + filtered_traces + }; + + Ok(filtered_traces) + } + /// Returns all receipts of the block pub fn mined_receipts(&self, hash: B256) -> Option> { let block = self.mined_block_by_hash(hash)?; @@ -2033,6 +2148,11 @@ impl Backend { .base_fee_per_gas .unwrap_or_else(|| self.base_fee()) .saturating_add(t.tx().tx().max_priority_fee_per_gas), + TypedTransaction::EIP7702(t) => block + .header + .base_fee_per_gas + .unwrap_or_else(|| self.base_fee()) + .saturating_add(t.tx().max_priority_fee_per_gas), TypedTransaction::Deposit(_) => 0_u128, }; @@ -2067,6 +2187,7 @@ impl Backend { TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedReceipt::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt { inner: receipt_with_bloom, deposit_nonce: r.deposit_nonce, @@ -2088,6 +2209,7 @@ impl Backend { state_root: Some(block.header.state_root), blob_gas_price: Some(blob_gas_price), blob_gas_used, + authorization_list: None, }; Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) @@ -2150,7 +2272,7 @@ impl Backend { Ok(None) } - fn mined_transaction_by_block_hash_and_index( + pub fn mined_transaction_by_block_hash_and_index( &self, block_hash: B256, index: Index, @@ -2189,7 +2311,7 @@ impl Backend { Ok(None) } - fn mined_transaction_by_hash(&self, hash: B256) -> Option> { + pub fn mined_transaction_by_hash(&self, hash: B256) -> Option> { let (info, block) = { let storage = self.blockchain.storage.read(); let MinedTransaction { info, block_hash, .. } = @@ -2239,7 +2361,7 @@ impl Backend { let account_proof = AccountProof { address, balance: account.info.balance, - nonce: U64::from(account.info.nonce), + nonce: account.info.nonce, code_hash: account.info.code_hash, storage_hash: storage_root(&account.storage), account_proof: proof, @@ -2379,7 +2501,7 @@ impl TransactionValidator for Backend { // Light checks first: see if the blob fee cap is too low. if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price { - if max_fee_per_blob_gas.to::() < blob_gas_and_price.blob_gasprice { + if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice { warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); return Err(InvalidTransactionError::BlobFeeCapTooLow); } diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index dd52eedfa..9d66fac28 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -83,7 +83,7 @@ where let mut account_info = cache_db.basic_ref(*account)?.unwrap_or_default(); if let Some(nonce) = account_overrides.nonce { - account_info.nonce = nonce.to::(); + account_info.nonce = nonce; } if let Some(code) = &account_overrides.code { account_info.code = Some(Bytecode::new_raw(code.to_vec().into())); diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 3531057ad..692e669e5 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -1,24 +1,34 @@ //! In-memory blockchain storage use crate::eth::{ backend::{ - db::{MaybeFullDatabase, SerializableBlock, StateDb}, + db::{MaybeFullDatabase, SerializableBlock, SerializableTransaction, StateDb}, mem::cache::DiskStateCache, }, + error::BlockchainError, pool::transactions::PoolTransaction, }; use alloy_primitives::{Bytes, TxHash, B256, U256, U64}; -use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo}; -use alloy_rpc_types_trace::{ - geth::{DefaultFrame, GethDefaultTracingOptions}, - parity::LocalizedTransactionTrace, +use alloy_rpc_types::{ + trace::{ + geth::{ + FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, + GethDebugTracingOptions, GethTrace, NoopFrame, + }, + otterscan::{InternalOperation, OperationType}, + parity::LocalizedTransactionTrace, + }, + BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo, }; use anvil_core::eth::{ block::{Block, PartialHeader}, transaction::{MaybeImpersonatedTransaction, ReceiptResponse, TransactionInfo, TypedReceipt}, }; +use anvil_rpc::error::RpcError; use foundry_evm::{ revm::primitives::Env, - traces::{GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig}, + traces::{ + CallKind, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig, + }, }; use parking_lot::RwLock; use std::{ @@ -157,7 +167,7 @@ impl InMemoryBlockStates { if let Some(state) = self.on_disk_states.get_mut(hash) { if let Some(cached) = self.disk_cache.read(*hash) { state.init_from_snapshot(cached); - return Some(state) + return Some(state); } } None @@ -324,6 +334,10 @@ impl BlockchainStorage { self.blocks.values().map(|block| block.clone().into()).collect() } + pub fn serialized_transactions(&self) -> Vec { + self.transactions.values().map(|tx: &MinedTransaction| tx.clone().into()).collect() + } + /// Deserialize and add all blocks data to the backend storage pub fn load_blocks(&mut self, serializable_blocks: Vec) { for serializable_block in serializable_blocks.iter() { @@ -334,6 +348,14 @@ impl BlockchainStorage { self.hashes.insert(U64::from(block_number), block_hash); } } + + /// Deserialize and add all blocks data to the backend storage + pub fn load_transactions(&mut self, serializable_transactions: Vec) { + for serializable_transaction in serializable_transactions.iter() { + let transaction: MinedTransaction = serializable_transaction.clone().into(); + self.transactions.insert(transaction.info.transaction_hash, transaction); + } + } } /// A simple in-memory blockchain @@ -419,13 +441,76 @@ impl MinedTransaction { }) } - pub fn geth_trace(&self, opts: GethDefaultTracingOptions) -> DefaultFrame { - GethTraceBuilder::new(self.info.traces.clone(), TracingInspectorConfig::default_geth()) - .geth_traces( - self.receipt.cumulative_gas_used() as u64, - self.info.out.clone().unwrap_or_default().0.into(), - opts, - ) + pub fn ots_internal_operations(&self) -> Vec { + self.info + .traces + .iter() + .filter_map(|node| { + let r#type = match node.trace.kind { + _ if node.is_selfdestruct() => OperationType::OpSelfDestruct, + CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer, + CallKind::Create => OperationType::OpCreate, + CallKind::Create2 => OperationType::OpCreate2, + _ => return None, + }; + let mut from = node.trace.caller; + let mut to = node.trace.address; + let mut value = node.trace.value; + if node.is_selfdestruct() { + from = node.trace.address; + to = node.trace.selfdestruct_refund_target.unwrap_or_default(); + value = node.trace.selfdestruct_transferred_value.unwrap_or_default(); + } + Some(InternalOperation { r#type, from, to, value }) + }) + .collect() + } + + pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> Result { + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; + + if let Some(tracer) = tracer { + match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::FourByteTracer => { + let inspector = FourByteInspector::default(); + return Ok(FourByteFrame::from(inspector).into()); + } + GethDebugBuiltInTracerType::CallTracer => { + return match tracer_config.into_call_config() { + Ok(call_config) => Ok(GethTraceBuilder::new( + self.info.traces.clone(), + TracingInspectorConfig::from_geth_config(&config), + ) + .geth_call_traces( + call_config, + self.receipt.cumulative_gas_used() as u64, + ) + .into()), + Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), + }; + } + GethDebugBuiltInTracerType::PreStateTracer | + GethDebugBuiltInTracerType::NoopTracer | + GethDebugBuiltInTracerType::MuxTracer => {} + }, + GethDebugTracerType::JsTracer(_code) => {} + } + + return Ok(NoopFrame::default().into()); + } + + // default structlog tracer + Ok(GethTraceBuilder::new( + self.info.traces.clone(), + TracingInspectorConfig::from_geth_config(&config), + ) + .geth_traces( + self.receipt.cumulative_gas_used() as u64, + self.info.out.clone().unwrap_or_default(), + opts.config, + ) + .into()) } } @@ -517,7 +602,8 @@ mod tests { } } - // verifies that blocks in BlockchainStorage remain the same when dumped and reloaded + // verifies that blocks and transactions in BlockchainStorage remain the same when dumped and + // reloaded #[test] fn test_storage_dump_reload_cycle() { let mut dump_storage = BlockchainStorage::empty(); @@ -535,10 +621,12 @@ mod tests { dump_storage.blocks.insert(block_hash, block); let serialized_blocks = dump_storage.serialized_blocks(); + let serialized_transactions = dump_storage.serialized_transactions(); let mut load_storage = BlockchainStorage::empty(); load_storage.load_blocks(serialized_blocks); + load_storage.load_transactions(serialized_transactions); let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); assert_eq!(loaded_block.header.gas_limit, partial_header.gas_limit); diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index fe31fbc2a..b8b475deb 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -85,6 +85,8 @@ pub enum BlockchainError { EIP2930TransactionUnsupportedAtHardfork, #[error("EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later.")] EIP4844TransactionUnsupportedAtHardfork, + #[error("EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later.")] + EIP7702TransactionUnsupportedAtHardfork, #[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")] DepositTransactionUnsupported, #[error("Excess blob gas not set.")] @@ -418,6 +420,9 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => { + RpcError::invalid_params(err.to_string()) + } err @ BlockchainError::DepositTransactionUnsupported => { RpcError::invalid_params(err.to_string()) } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index e0c8685fd..45b33ad0f 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -2,6 +2,7 @@ use crate::eth::{ backend::{info::StorageInfo, notifications::NewBlockNotifications}, error::BlockchainError, }; +use alloy_consensus::Header; use alloy_eips::{ calc_next_block_base_fee, eip1559::BaseFeeParams, eip4844::MAX_DATA_GAS_PER_BLOCK, }; @@ -190,8 +191,6 @@ pub struct FeeHistoryService { cache: FeeHistoryCache, /// number of items to consider fee_history_limit: u64, - // current fee info - fees: FeeManager, /// a type that can fetch ethereum-storage data storage_info: StorageInfo, } @@ -200,16 +199,9 @@ impl FeeHistoryService { pub fn new( new_blocks: NewBlockNotifications, cache: FeeHistoryCache, - fees: FeeManager, storage_info: StorageInfo, ) -> Self { - Self { - new_blocks, - cache, - fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, - fees, - storage_info, - } + Self { new_blocks, cache, fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, storage_info } } /// Returns the configured history limit @@ -218,13 +210,17 @@ impl FeeHistoryService { } /// Inserts a new cache entry for the given block - pub(crate) fn insert_cache_entry_for_block(&self, hash: B256) { - let (result, block_number) = self.create_cache_entry(hash); + pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) { + let (result, block_number) = self.create_cache_entry(hash, header); self.insert_cache_entry(result, block_number); } /// Create a new history entry for the block - fn create_cache_entry(&self, hash: B256) -> (FeeHistoryCacheItem, Option) { + fn create_cache_entry( + &self, + hash: B256, + header: &Header, + ) -> (FeeHistoryCacheItem, Option) { // percentile list from 0.0 to 100.0 with a 0.5 resolution. // this will create 200 percentile points let reward_percentiles: Vec = { @@ -239,16 +235,18 @@ impl FeeHistoryService { }; let mut block_number: Option = None; - let base_fee = self.fees.base_fee(); - let excess_blob_gas_and_price = self.fees.excess_blob_gas_and_price(); + let base_fee = header.base_fee_per_gas.unwrap_or_default(); + let excess_blob_gas = header.excess_blob_gas; + let blob_gas_used = header.blob_gas_used; + let base_fee_per_blob_gas = header.blob_fee(); let mut item = FeeHistoryCacheItem { base_fee, gas_used_ratio: 0f64, blob_gas_used_ratio: 0f64, rewards: Vec::new(), - excess_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.excess_blob_gas as u128), - base_fee_per_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.blob_gasprice), - blob_gas_used: excess_blob_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas, + base_fee_per_blob_gas, + blob_gas_used, }; let current_block = self.storage_info.block(hash); @@ -287,6 +285,10 @@ impl FeeHistoryService { .tx() .max_priority_fee_per_gas .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)), + Some(TypedTransaction::EIP7702(t)) => t + .tx() + .max_priority_fee_per_gas + .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), Some(TypedTransaction::Deposit(_)) => 0, None => 0, }; @@ -345,10 +347,8 @@ impl Future for FeeHistoryService { let pin = self.get_mut(); while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) { - let hash = notification.hash; - // add the imported block. - pin.insert_cache_entry_for_block(hash); + pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref()); } Poll::Pending diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index b559351fe..ddd271850 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -12,10 +12,10 @@ use std::{ fmt, pin::Pin, sync::Arc, - task::{Context, Poll}, + task::{ready, Context, Poll}, time::Duration, }; -use tokio::time::Interval; +use tokio::time::{Interval, MissedTickBehavior}; #[derive(Clone, Debug)] pub struct Miner { @@ -25,12 +25,32 @@ pub struct Miner { /// /// This will register the task so we can manually wake it up if the mining mode was changed inner: Arc, + /// Transactions included into the pool before any others are. + /// Done once on startup. + force_transactions: Option>>, } impl Miner { - /// Returns a new miner with that operates in the given `mode` + /// Returns a new miner with that operates in the given `mode`. pub fn new(mode: MiningMode) -> Self { - Self { mode: Arc::new(RwLock::new(mode)), inner: Default::default() } + Self { + mode: Arc::new(RwLock::new(mode)), + inner: Default::default(), + force_transactions: None, + } + } + + /// Provide transactions that will cause a block to be mined with transactions + /// as soon as the miner is polled. + /// Providing an empty list of transactions will cause the miner to mine an empty block assuming + /// there are not other transactions in the pool. + pub fn with_forced_transactions( + mut self, + force_transactions: Option>, + ) -> Self { + self.force_transactions = + force_transactions.map(|tx| tx.into_iter().map(Arc::new).collect()); + self } /// Returns the write lock of the mining mode @@ -67,7 +87,13 @@ impl Miner { cx: &mut Context<'_>, ) -> Poll>> { self.inner.register(cx); - self.mode.write().poll(pool, cx) + let next = ready!(self.mode.write().poll(pool, cx)); + if let Some(mut transactions) = self.force_transactions.take() { + transactions.extend(next); + Poll::Ready(transactions) + } else { + Poll::Ready(next) + } } } @@ -149,7 +175,11 @@ impl FixedBlockTimeMiner { /// Creates a new instance with an interval of `duration` pub fn new(duration: Duration) -> Self { let start = tokio::time::Instant::now() + duration; - Self { interval: tokio::time::interval_at(start, duration) } + let mut interval = tokio::time::interval_at(start, duration); + // we use delay here, to ensure ticks are not shortened and to tick at multiples of interval + // from when tick was called rather than from start + interval.set_missed_tick_behavior(MissedTickBehavior::Delay); + Self { interval } } fn poll(&mut self, pool: &Arc, cx: &mut Context<'_>) -> Poll>> { diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index a830855d4..f174d79f5 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -1,17 +1,82 @@ -use super::types::{ - OtsBlockDetails, OtsBlockTransactions, OtsContractCreator, OtsInternalOperation, - OtsSearchTransactions, OtsTrace, -}; use crate::eth::{ error::{BlockchainError, Result}, macros::node_info, EthApi, }; use alloy_primitives::{Address, Bytes, B256, U256}; -use alloy_rpc_types::{Block, BlockId, BlockNumberOrTag as BlockNumber}; -use alloy_rpc_types_trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; +use alloy_rpc_types::{ + trace::{ + otterscan::{ + BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions, + OtsReceipt, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, + }, + parity::{ + Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, + RewardAction, TraceOutput, + }, + }, + Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, +}; use itertools::Itertools; +use futures::future::join_all; + +pub fn mentions_address(trace: LocalizedTransactionTrace, address: Address) -> Option { + match (trace.trace.action, trace.trace.result) { + (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => { + trace.transaction_hash + } + (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. }))) + if created_address == address => + { + trace.transaction_hash + } + (Action::Create(CreateAction { from, .. }), _) if from == address => trace.transaction_hash, + (Action::Reward(RewardAction { author, .. }), _) if author == address => { + trace.transaction_hash + } + _ => None, + } +} + +/// Converts the list of traces for a transaction into the expected Otterscan format, as +/// specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_tracetransaction) spec +pub fn batch_build_ots_traces(traces: Vec) -> Vec { + traces + .into_iter() + .filter_map(|trace| { + let output = trace + .trace + .result + .map(|r| match r { + TraceOutput::Call(output) => output.output, + TraceOutput::Create(output) => output.code, + }) + .unwrap_or_default(); + match trace.trace.action { + Action::Call(call) => Some(TraceEntry { + r#type: match call.call_type { + CallType::Call => "CALL", + CallType::CallCode => "CALLCODE", + CallType::DelegateCall => "DELEGATECALL", + CallType::StaticCall => "STATICCALL", + CallType::AuthCall => "AUTHCALL", + CallType::None => "NONE", + } + .to_string(), + depth: trace.trace.trace_address.len() as u32, + from: call.from, + to: call.to, + value: call.value, + input: call.input, + output, + }), + Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None, + } + }) + .collect() +} + impl EthApi { /// Otterscan currently requires this endpoint, even though it's not part of the `ots_*`. /// Ref: @@ -35,15 +100,12 @@ impl EthApi { /// Trace internal ETH transfers, contracts creation (CREATE/CREATE2) and self-destructs for a /// certain transaction. - pub async fn ots_get_internal_operations( - &self, - hash: B256, - ) -> Result> { + pub async fn ots_get_internal_operations(&self, hash: B256) -> Result> { node_info!("ots_getInternalOperations"); self.backend .mined_transaction(hash) - .map(OtsInternalOperation::batch_build) + .map(|tx| tx.ots_internal_operations()) .ok_or_else(|| BlockchainError::DataUnavailable) } @@ -55,10 +117,10 @@ impl EthApi { } /// Trace a transaction and generate a trace call tree. - pub async fn ots_trace_transaction(&self, hash: B256) -> Result> { + pub async fn ots_trace_transaction(&self, hash: B256) -> Result> { node_info!("ots_traceTransaction"); - Ok(OtsTrace::batch_build(self.backend.trace_transaction(hash).await?)) + Ok(batch_build_ots_traces(self.backend.trace_transaction(hash).await?)) } /// Given a transaction hash, returns its raw revert reason. @@ -67,7 +129,7 @@ impl EthApi { if let Some(receipt) = self.backend.mined_transaction_receipt(hash) { if !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() { - return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())) + return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())); } } @@ -77,12 +139,11 @@ impl EthApi { /// For simplicity purposes, we return the entire block instead of emptying the values that /// Otterscan doesn't want. This is the original purpose of the endpoint (to save bandwidth), /// but it doesn't seem necessary in the context of an anvil node - pub async fn ots_get_block_details(&self, number: BlockNumber) -> Result { + pub async fn ots_get_block_details(&self, number: BlockNumber) -> Result { node_info!("ots_getBlockDetails"); if let Some(block) = self.backend.block_by_number(number).await? { - let ots_block = OtsBlockDetails::build(block, &self.backend).await?; - + let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) @@ -92,12 +153,11 @@ impl EthApi { /// For simplicity purposes, we return the entire block instead of emptying the values that /// Otterscan doesn't want. This is the original purpose of the endpoint (to save bandwidth), /// but it doesn't seem necessary in the context of an anvil node - pub async fn ots_get_block_details_by_hash(&self, hash: B256) -> Result { + pub async fn ots_get_block_details_by_hash(&self, hash: B256) -> Result { node_info!("ots_getBlockDetailsByHash"); if let Some(block) = self.backend.block_by_hash(hash).await? { - let ots_block = OtsBlockDetails::build(block, &self.backend).await?; - + let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) @@ -115,7 +175,7 @@ impl EthApi { node_info!("ots_getBlockTransactions"); match self.backend.block_by_number_full(number.into()).await? { - Some(block) => OtsBlockTransactions::build(block, &self.backend, page, page_size).await, + Some(block) => self.build_ots_block_tx(block, page, page_size).await, None => Err(BlockchainError::BlockNotFound), } } @@ -126,7 +186,7 @@ impl EthApi { address: Address, block_number: u64, page_size: usize, - ) -> Result { + ) -> Result { node_info!("ots_searchTransactionsBefore"); let best = self.backend.best_number(); @@ -145,11 +205,11 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) + .filter_map(|trace| mentions_address(trace, address)) .unique(); if res.len() >= page_size { - break + break; } res.extend(hashes); @@ -160,7 +220,7 @@ impl EthApi { } } - OtsSearchTransactions::build(res, &self.backend, first_page, last_page).await + self.build_ots_search_transactions(res, first_page, last_page).await } /// Address history navigation. searches forward from certain point in time. @@ -169,7 +229,7 @@ impl EthApi { address: Address, block_number: u64, page_size: usize, - ) -> Result { + ) -> Result { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); @@ -192,11 +252,11 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) + .filter_map(|trace| mentions_address(trace, address)) .unique(); if res.len() >= page_size { - break + break; } res.extend(hashes); @@ -209,7 +269,7 @@ impl EthApi { // Results are always sent in reverse chronological order, according to the Otterscan spec res.reverse(); - OtsSearchTransactions::build(res, &self.backend, first_page, last_page).await + self.build_ots_search_transactions(res, first_page, last_page).await } /// Given a sender address and a nonce, returns the tx hash or null if not found. It returns @@ -229,7 +289,7 @@ impl EthApi { if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await { for tx in txs { if U256::from(tx.nonce) == nonce && tx.from == address { - return Ok(Some(tx.hash)) + return Ok(Some(tx.hash)); } } } @@ -240,10 +300,7 @@ impl EthApi { /// Given an ETH contract address, returns the tx hash and the direct address who created the /// contract. - pub async fn ots_get_contract_creator( - &self, - addr: Address, - ) -> Result> { + pub async fn ots_get_contract_creator(&self, addr: Address) -> Result> { node_info!("ots_getContractCreator"); let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default(); @@ -258,10 +315,10 @@ impl EthApi { Action::Create(CreateAction { from, .. }), Some(TraceOutput::Create(CreateOutput { address, .. })), ) if address == addr => { - return Ok(Some(OtsContractCreator { + return Ok(Some(ContractCreator { hash: trace.transaction_hash.unwrap(), creator: from, - })) + })); } _ => {} } @@ -271,4 +328,136 @@ impl EthApi { Ok(None) } + /// The response for ots_getBlockDetails includes an `issuance` object that requires computing + /// the total gas spent in a given block. + /// + /// The only way to do this with the existing API is to explicitly fetch all receipts, to get + /// their `gas_used`. This would be extremely inefficient in a real blockchain RPC, but we can + /// get away with that in this context. + /// + /// The [original spec](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) + /// also mentions we can hardcode `transactions` and `logsBloom` to an empty array to save + /// bandwidth, because fields weren't intended to be used in the Otterscan UI at this point. + /// + /// This has two problems though: + /// - It makes the endpoint too specific to Otterscan's implementation + /// - It breaks the abstraction built in `OtsBlock` which computes `transaction_count` + /// based on the existing list. + /// + /// Therefore we keep it simple by keeping the data in the response + pub async fn build_ots_block_details(&self, block: Block) -> Result { + if block.transactions.is_uncle() { + return Err(BlockchainError::DataUnavailable); + } + let receipts_futs = + block.transactions.hashes().map(|hash| async { self.transaction_receipt(*hash).await }); + + // fetch all receipts + let receipts = join_all(receipts_futs) + .await + .into_iter() + .map(|r| match r { + Ok(Some(r)) => Ok(r), + _ => Err(BlockchainError::DataUnavailable), + }) + .collect::>>()?; + + let total_fees = receipts + .iter() + .fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price); + + Ok(BlockDetails { + block: block.into(), + total_fees: U256::from(total_fees), + // issuance has no meaningful value in anvil's backend. just default to 0 + issuance: Default::default(), + }) + } + + /// Fetches all receipts for the blocks's transactions, as required by the + /// [`ots_getBlockTransactions`] endpoint spec, and returns the final response object. + /// + /// [`ots_getBlockTransactions`]: https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails + pub async fn build_ots_block_tx( + &self, + mut block: Block, + page: usize, + page_size: usize, + ) -> Result { + if block.transactions.is_uncle() { + return Err(BlockchainError::DataUnavailable); + } + + block.transactions = match block.transactions { + BlockTransactions::Full(txs) => BlockTransactions::Full( + txs.into_iter().skip(page * page_size).take(page_size).collect(), + ), + BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( + txs.into_iter().skip(page * page_size).take(page_size).collect(), + ), + BlockTransactions::Uncle => unreachable!(), + }; + + let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(*hash)); + + let receipts = join_all(receipt_futs) + .await + .into_iter() + .map(|r| match r { + Ok(Some(r)) => { + let timestamp = + self.backend.get_block(r.block_number.unwrap()).unwrap().header.timestamp; + let receipt = r.map_inner(OtsReceipt::from); + let res = OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }; + Ok(res) + } + _ => Err(BlockchainError::DataUnavailable), + }) + .collect::>>()?; + + let fullblock: OtsBlock = block.into(); + + Ok(OtsBlockTransactions { fullblock, receipts }) + } + + pub async fn build_ots_search_transactions( + &self, + hashes: Vec, + first_page: bool, + last_page: bool, + ) -> Result { + let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await }); + + let txs = join_all(txs_futs) + .await + .into_iter() + .map(|t| match t { + Ok(Some(t)) => Ok(t.inner), + _ => Err(BlockchainError::DataUnavailable), + }) + .collect::>()?; + + let receipts = join_all(hashes.iter().map(|hash| async { + match self.transaction_receipt(*hash).await { + Ok(Some(receipt)) => { + let timestamp = self + .backend + .get_block(receipt.block_number.unwrap()) + .unwrap() + .header + .timestamp; + let receipt = receipt.map_inner(OtsReceipt::from); + let res = OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }; + Ok(res) + } + Ok(None) => Err(BlockchainError::DataUnavailable), + Err(e) => Err(e), + } + })) + .await + .into_iter() + .collect::>>()?; + + Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page }) + } } diff --git a/crates/anvil/src/eth/otterscan/mod.rs b/crates/anvil/src/eth/otterscan/mod.rs index 8389f117b..e5fdf85ee 100644 --- a/crates/anvil/src/eth/otterscan/mod.rs +++ b/crates/anvil/src/eth/otterscan/mod.rs @@ -1,2 +1 @@ pub mod api; -pub mod types; diff --git a/crates/anvil/src/eth/otterscan/types.rs b/crates/anvil/src/eth/otterscan/types.rs deleted file mode 100644 index 048e264a3..000000000 --- a/crates/anvil/src/eth/otterscan/types.rs +++ /dev/null @@ -1,356 +0,0 @@ -use crate::eth::{ - backend::mem::{storage::MinedTransaction, Backend}, - error::{BlockchainError, Result}, -}; -use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256}; -use alloy_rpc_types::{Block, BlockTransactions, Transaction}; -use alloy_rpc_types_trace::parity::{ - Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, - RewardAction, TraceOutput, -}; -use alloy_serde::WithOtherFields; -use anvil_core::eth::transaction::ReceiptResponse; -use foundry_evm::traces::CallKind; -use futures::future::join_all; -use serde::Serialize; -use serde_repr::Serialize_repr; - -/// Patched Block struct, to include the additional `transactionCount` field expected by Otterscan -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OtsBlock { - #[serde(flatten)] - pub block: Block, - pub transaction_count: usize, -} - -/// Block structure with additional details regarding fees and issuance -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OtsBlockDetails { - pub block: OtsBlock, - pub total_fees: U256, - pub issuance: Issuance, -} - -/// Issuance information for a block. Expected by Otterscan in ots_getBlockDetails calls -#[derive(Debug, Default, Serialize)] -pub struct Issuance { - block_reward: U256, - uncle_reward: U256, - issuance: U256, -} - -/// Holds both transactions and receipts for a block -#[derive(Clone, Serialize, Debug)] -pub struct OtsBlockTransactions { - pub fullblock: OtsBlock, - pub receipts: Vec, -} - -/// Patched Receipt struct, to include the additional `timestamp` field expected by Otterscan -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OtsTransactionReceipt { - #[serde(flatten)] - receipt: ReceiptResponse, - timestamp: u64, -} - -/// Information about the creator address and transaction for a contract -#[derive(Debug, Serialize)] -pub struct OtsContractCreator { - pub hash: B256, - pub creator: Address, -} - -/// Paginated search results of an account's history -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OtsSearchTransactions { - pub txs: Vec>, - pub receipts: Vec, - pub first_page: bool, - pub last_page: bool, -} - -/// Otterscan format for listing relevant internal operations. -/// -/// Ref: -#[derive(Debug, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OtsInternalOperation { - pub r#type: OtsInternalOperationType, - pub from: Address, - pub to: Address, - pub value: U256, -} - -/// Types of internal operations recognized by Otterscan. -/// -/// Ref: -#[derive(Debug, PartialEq, Serialize_repr)] -#[repr(u8)] -pub enum OtsInternalOperationType { - Transfer = 0, - SelfDestruct = 1, - Create = 2, - Create2 = 3, -} - -/// Otterscan's representation of a trace -#[derive(Debug, PartialEq, Serialize)] -pub struct OtsTrace { - pub r#type: OtsTraceType, - pub depth: usize, - pub from: Address, - pub to: Address, - pub value: U256, - pub input: Bytes, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub output: Option, -} - -/// The type of call being described by an Otterscan trace. Only CALL, STATICCALL and DELEGATECALL -/// are represented -#[derive(Debug, PartialEq, Serialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum OtsTraceType { - Call, - StaticCall, - DelegateCall, -} - -impl OtsBlockDetails { - /// The response for ots_getBlockDetails includes an `issuance` object that requires computing - /// the total gas spent in a given block. - /// - /// The only way to do this with the existing API is to explicitly fetch all receipts, to get - /// their `gas_used`. This would be extremely inefficient in a real blockchain RPC, but we can - /// get away with that in this context. - /// - /// The [original spec](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) - /// also mentions we can hardcode `transactions` and `logsBloom` to an empty array to save - /// bandwidth, because fields weren't intended to be used in the Otterscan UI at this point. - /// - /// This has two problems though: - /// - It makes the endpoint too specific to Otterscan's implementation - /// - It breaks the abstraction built in `OtsBlock` which computes `transaction_count` - /// based on the existing list. - /// - /// Therefore we keep it simple by keeping the data in the response - pub async fn build(block: Block, backend: &Backend) -> Result { - if block.transactions.is_uncle() { - return Err(BlockchainError::DataUnavailable); - } - let receipts_futs = block - .transactions - .hashes() - .map(|hash| async { backend.transaction_receipt(*hash).await }); - - // fetch all receipts - let receipts = join_all(receipts_futs) - .await - .into_iter() - .map(|r| match r { - Ok(Some(r)) => Ok(r), - _ => Err(BlockchainError::DataUnavailable), - }) - .collect::>>()?; - - let total_fees = receipts - .iter() - .fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price); - - Ok(Self { - block: block.into(), - total_fees: U256::from(total_fees), - // issuance has no meaningful value in anvil's backend. just default to 0 - issuance: Default::default(), - }) - } -} - -/// Converts a regular block into the patched OtsBlock format -/// which includes the `transaction_count` field -impl From for OtsBlock { - fn from(block: Block) -> Self { - Self { transaction_count: block.transactions.len(), block } - } -} - -impl OtsBlockTransactions { - /// Fetches all receipts for the blocks's transactions, as required by the - /// [`ots_getBlockTransactions`] endpoint spec, and returns the final response object. - /// - /// [`ots_getBlockTransactions`]: https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails - pub async fn build( - mut block: Block, - backend: &Backend, - page: usize, - page_size: usize, - ) -> Result { - if block.transactions.is_uncle() { - return Err(BlockchainError::DataUnavailable); - } - - block.transactions = match block.transactions { - BlockTransactions::Full(txs) => BlockTransactions::Full( - txs.into_iter().skip(page * page_size).take(page_size).collect(), - ), - BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( - txs.into_iter().skip(page * page_size).take(page_size).collect(), - ), - BlockTransactions::Uncle => unreachable!(), - }; - - let receipt_futs = - block.transactions.hashes().map(|hash| backend.transaction_receipt(*hash)); - - let receipts = join_all(receipt_futs) - .await - .into_iter() - .map(|r| match r { - Ok(Some(r)) => Ok(r), - _ => Err(BlockchainError::DataUnavailable), - }) - .collect::>()?; - - let fullblock: OtsBlock = block.into(); - - Ok(Self { fullblock, receipts }) - } -} - -impl OtsSearchTransactions { - /// Constructs the final response object for both [`ots_searchTransactionsBefore` and - /// `ots_searchTransactionsAfter`](lrequires not only the transactions, but also the - /// corresponding receipts, which are fetched here before constructing the final) - pub async fn build( - hashes: Vec, - backend: &Backend, - first_page: bool, - last_page: bool, - ) -> Result { - let txs_futs = hashes.iter().map(|hash| async { backend.transaction_by_hash(*hash).await }); - - let txs: Vec<_> = join_all(txs_futs) - .await - .into_iter() - .map(|t| match t { - Ok(Some(t)) => Ok(t), - _ => Err(BlockchainError::DataUnavailable), - }) - .collect::>()?; - - join_all(hashes.iter().map(|hash| async { - match backend.transaction_receipt(*hash).await { - Ok(Some(receipt)) => { - let timestamp = - backend.get_block(receipt.block_number.unwrap()).unwrap().header.timestamp; - Ok(OtsTransactionReceipt { receipt, timestamp }) - } - Ok(None) => Err(BlockchainError::DataUnavailable), - Err(e) => Err(e), - } - })) - .await - .into_iter() - .collect::>>() - .map(|receipts| Self { txs, receipts, first_page, last_page }) - } - - pub fn mentions_address( - trace: LocalizedTransactionTrace, - address: Address, - ) -> Option> { - match (trace.trace.action, trace.trace.result) { - (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => { - trace.transaction_hash - } - (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. }))) - if created_address == address => - { - trace.transaction_hash - } - (Action::Create(CreateAction { from, .. }), _) if from == address => { - trace.transaction_hash - } - (Action::Reward(RewardAction { author, .. }), _) if author == address => { - trace.transaction_hash - } - _ => None, - } - } -} - -impl OtsInternalOperation { - /// Converts a batch of traces into a batch of internal operations, to comply with the spec for - /// [`ots_getInternalOperations`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getinternaloperations) - pub fn batch_build(traces: MinedTransaction) -> Vec { - traces - .info - .traces - .iter() - .filter_map(|node| { - let r#type = match node.trace.kind { - _ if node.is_selfdestruct() => OtsInternalOperationType::SelfDestruct, - CallKind::Call if node.trace.value != U256::ZERO => { - OtsInternalOperationType::Transfer - } - CallKind::Create => OtsInternalOperationType::Create, - CallKind::Create2 => OtsInternalOperationType::Create2, - _ => return None, - }; - let mut from = node.trace.caller; - let mut to = node.trace.address; - if node.is_selfdestruct() { - from = node.trace.address; - to = node.trace.selfdestruct_refund_target.unwrap_or_default(); - } - Some(Self { r#type, from, to, value: node.trace.value }) - }) - .collect() - } -} - -impl OtsTrace { - /// Converts the list of traces for a transaction into the expected Otterscan format, as - /// specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_tracetransaction) spec - pub fn batch_build(traces: Vec) -> Vec { - traces - .into_iter() - .filter_map(|trace| match trace.trace.action { - Action::Call(call) => { - if let Ok(ots_type) = call.call_type.try_into() { - Some(Self { - r#type: ots_type, - depth: trace.trace.trace_address.len(), - from: call.from, - to: call.to, - value: call.value, - input: call.input.0.into(), - output: None, - }) - } else { - None - } - } - Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None, - }) - .collect() - } -} - -impl TryFrom for OtsTraceType { - type Error = (); - - fn try_from(value: CallType) -> std::result::Result { - match value { - CallType::Call => Ok(Self::Call), - CallType::StaticCall => Ok(Self::StaticCall), - CallType::DelegateCall => Ok(Self::DelegateCall), - _ => Err(()), - } - } -} diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 026268231..502ee1e78 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,5 +1,6 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; use alloy_primitives::{Address, TxHash}; +use alloy_rpc_types::Transaction as RpcTransaction; use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; use parking_lot::RwLock; use std::{ @@ -107,6 +108,20 @@ impl fmt::Debug for PoolTransaction { } } +impl TryFrom for PoolTransaction { + type Error = eyre::Error; + fn try_from(transaction: RpcTransaction) -> Result { + let typed_transaction = TypedTransaction::try_from(transaction)?; + let pending_transaction = PendingTransaction::new(typed_transaction)?; + Ok(Self { + pending_transaction, + requires: vec![], + provides: vec![], + priority: TransactionPriority(0), + }) + } +} + /// A waiting pool of transaction that are pending, but not yet ready to be included in a new block. /// /// Keeps a set of transactions that are waiting for other transactions diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index c122d54dd..d921b18d3 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,5 +1,5 @@ use crate::eth::error::BlockchainError; -use alloy_consensus::{SignableTransaction, Signed}; +use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::TxSignerSync; use alloy_primitives::{Address, Signature, B256}; @@ -121,21 +121,15 @@ pub fn build_typed_transaction( signature: Signature, ) -> Result { let tx = match request { - TypedTransactionRequest::Legacy(tx) => { - let sighash = tx.signature_hash(); - TypedTransaction::Legacy(Signed::new_unchecked(tx, signature, sighash)) - } + TypedTransactionRequest::Legacy(tx) => TypedTransaction::Legacy(tx.into_signed(signature)), TypedTransactionRequest::EIP2930(tx) => { - let sighash = tx.signature_hash(); - TypedTransaction::EIP2930(Signed::new_unchecked(tx, signature, sighash)) + TypedTransaction::EIP2930(tx.into_signed(signature)) } TypedTransactionRequest::EIP1559(tx) => { - let sighash = tx.signature_hash(); - TypedTransaction::EIP1559(Signed::new_unchecked(tx, signature, sighash)) + TypedTransaction::EIP1559(tx.into_signed(signature)) } TypedTransactionRequest::EIP4844(tx) => { - let sighash = tx.signature_hash(); - TypedTransaction::EIP4844(Signed::new_unchecked(tx, signature, sighash)) + TypedTransaction::EIP4844(tx.into_signed(signature)) } TypedTransactionRequest::Deposit(tx) => { let DepositTransactionRequest { diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index 3c6eb0aa8..de4c43c18 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -21,6 +21,8 @@ pub enum Hardfork { Paris, Shanghai, Cancun, + Prague, + PragueEOF, #[default] Latest, } @@ -45,6 +47,8 @@ impl Hardfork { Self::Paris => 15537394, Self::Shanghai => 17034870, Self::Cancun | Self::Latest => 19426587, + // TODO: add block after activation + Self::Prague | Self::PragueEOF => unreachable!(), } } } @@ -72,6 +76,8 @@ impl FromStr for Hardfork { "paris" | "merge" | "15" => Self::Paris, "shanghai" | "16" => Self::Shanghai, "cancun" | "17" => Self::Cancun, + "prague" | "18" => Self::Prague, + "pragueeof" | "19" | "prague-eof" => Self::PragueEOF, "latest" => Self::Latest, _ => return Err(format!("Unknown hardfork {s}")), }; @@ -99,6 +105,9 @@ impl From for SpecId { Hardfork::Paris => Self::MERGE, Hardfork::Shanghai => Self::SHANGHAI, Hardfork::Cancun | Hardfork::Latest => Self::CANCUN, + Hardfork::Prague => Self::PRAGUE, + // TODO: switch to latest after activation + Hardfork::PragueEOF => Self::PRAGUE_EOF, } } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 471f7b05c..56005dd60 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -45,7 +45,7 @@ use tokio::{ mod service; mod config; -pub use config::{AccountGenerator, NodeConfig, CHAIN_ID, VERSION_MESSAGE}; +pub use config::{AccountGenerator, ForkChoice, NodeConfig, CHAIN_ID, VERSION_MESSAGE}; mod hardfork; pub use hardfork::Hardfork; @@ -128,7 +128,7 @@ pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle let backend = Arc::new(config.setup().await); if config.enable_auto_impersonate { - backend.auto_impersonate_account(true).await; + backend.auto_impersonate_account(true); } let fork = backend.get_fork(); @@ -156,7 +156,13 @@ pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle let listener = pool.add_ready_listener(); MiningMode::instant(max_transactions, listener) }; - let miner = Miner::new(mode); + + let miner = match &fork { + Some(fork) => { + Miner::new(mode).with_forced_transactions(fork.config.read().force_transactions.clone()) + } + _ => Miner::new(mode), + }; let dev_signer: Box = Box::new(DevSigner::new(signer_accounts)); let mut signers = vec![dev_signer]; @@ -172,19 +178,15 @@ pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle } } - let fees = backend.fees().clone(); let fee_history_cache = Arc::new(Mutex::new(Default::default())); let fee_history_service = FeeHistoryService::new( backend.new_block_notifications(), Arc::clone(&fee_history_cache), - fees, StorageInfo::new(Arc::clone(&backend)), ); // create an entry for the best block - if let Some(best_block) = - backend.get_block(backend.best_number()).map(|block| block.header.hash_slow()) - { - fee_history_service.insert_cache_entry_for_block(best_block); + if let Some(header) = backend.get_block(backend.best_number()).map(|block| block.header) { + fee_history_service.insert_cache_entry_for_block(header.hash_slow(), &header); } let filters = Filters::default(); diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index ba97ab7ef..e738254cb 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -8,6 +8,9 @@ use tracing_subscriber::{layer::Context, Layer}; /// The target that identifies the events intended to be logged to stdout pub(crate) const NODE_USER_LOG_TARGET: &str = "node::user"; +/// The target that identifies the events coming from the `console.log` invocations. +pub(crate) const EVM_CONSOLE_LOG_TARGET: &str = "node::console"; + /// A logger that listens for node related events and displays them. /// /// This layer is intended to be used as filter for `NODE_USER_LOG_TARGET` events that will @@ -30,15 +33,18 @@ where S: tracing::Subscriber, { fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { - if self.state.is_enabled() && metadata.target() == NODE_USER_LOG_TARGET { - Interest::always() + if metadata.target() == NODE_USER_LOG_TARGET || metadata.target() == EVM_CONSOLE_LOG_TARGET + { + Interest::sometimes() } else { Interest::never() } } fn enabled(&self, metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool { - self.state.is_enabled() && metadata.target() == NODE_USER_LOG_TARGET + self.state.is_enabled() && + (metadata.target() == NODE_USER_LOG_TARGET || + metadata.target() == EVM_CONSOLE_LOG_TARGET) } } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index d2ceb0dca..8cf13e844 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -6,9 +6,8 @@ use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; use alloy_network::AnyNetwork; use alloy_primitives::B256; use alloy_provider::Provider; -use alloy_rpc_types::Block; +use alloy_rpc_types::{anvil::Forking, Block}; use alloy_transport::Transport; -use anvil_core::types::Forking; use futures::StreamExt; use std::{fmt, future::Future}; use tokio::{runtime::Handle, task::JoinHandle}; diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index d83365eac..e6289e0b0 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -6,15 +6,15 @@ use crate::{ utils::http_provider_with_signer, }; use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{address, fixed_bytes, Address, U256, U64}; +use alloy_primitives::{address, fixed_bytes, Address, U256}; use alloy_provider::{ext::TxPoolApi, Provider}; -use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionRequest}; +use alloy_rpc_types::{ + anvil::{ForkedNetwork, Forking, Metadata, NodeEnvironment, NodeForkConfig, NodeInfo}, + BlockId, BlockNumberOrTag, TransactionRequest, +}; use alloy_serde::WithOtherFields; use anvil::{eth::api::CLIENT_VERSION, spawn, Hardfork, NodeConfig}; -use anvil_core::{ - eth::EthRequest, - types::{AnvilMetadata, ForkedNetwork, Forking, NodeEnvironment, NodeForkConfig, NodeInfo}, -}; +use anvil_core::eth::EthRequest; use foundry_evm::revm::primitives::SpecId; use std::{ str::FromStr, @@ -434,12 +434,13 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); + let hard_fork: &str = SpecId::CANCUN.into(); let expected_node_info = NodeInfo { - current_block_number: U64::from(0), + current_block_number: 0_u64, current_block_timestamp: 1, current_block_hash: block.header.hash.unwrap(), - hard_fork: SpecId::CANCUN, + hard_fork: hard_fork.to_string(), transaction_order: "fees".to_owned(), environment: NodeEnvironment { base_fee: U256::from_str("0x3b9aca00").unwrap().to(), @@ -470,11 +471,11 @@ async fn can_get_metadata() { let block = provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); - let expected_metadata = AnvilMetadata { + let expected_metadata = Metadata { latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, - client_version: CLIENT_VERSION, + client_version: CLIENT_VERSION.to_string(), instance_id: api.instance_id(), forked_network: None, snapshots: Default::default(), @@ -496,11 +497,11 @@ async fn can_get_metadata_on_fork() { let block = provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); - let expected_metadata = AnvilMetadata { + let expected_metadata = Metadata { latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, - client_version: CLIENT_VERSION, + client_version: CLIENT_VERSION.to_string(), instance_id: api.instance_id(), forked_network: Some(ForkedNetwork { chain_id, @@ -630,7 +631,8 @@ async fn test_fork_revert_call_latest_block_timestamp() { #[tokio::test(flavor = "multi_thread")] async fn can_remove_pool_transactions() { - let (api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = + spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(5)))).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 19842aa75..40d5a63a6 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -21,7 +21,7 @@ async fn can_send_eip4844_transaction() { let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); - let sidecar: SidecarBuilder = SidecarBuilder::from_slice("Hello World".as_bytes()); + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() diff --git a/crates/anvil/tests/it/eip7702.rs b/crates/anvil/tests/it/eip7702.rs new file mode 100644 index 000000000..2e7439e5d --- /dev/null +++ b/crates/anvil/tests/it/eip7702.rs @@ -0,0 +1,79 @@ +use crate::utils::http_provider; +use alloy_consensus::{transaction::TxEip7702, SignableTransaction}; +use alloy_eips::eip7702::OptionalNonce; +use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; +use alloy_primitives::{bytes, TxKind}; +use alloy_provider::Provider; +use alloy_rpc_types::{Authorization, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_signer::SignerSync; +use anvil::{spawn, Hardfork, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip7702_tx() { + let node_config = NodeConfig::test().with_hardfork(Some(Hardfork::Prague)); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + + // deploy simple contract forwarding calldata to LOG0 + // PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7) + // PUSH1(25) RETURN + let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + + let from = wallets[0].address(); + let tx = TransactionRequest::default() + .with_from(from) + .into_create() + .with_nonce(0) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_input(logger_bytecode); + + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert!(receipt.status()); + + let contract = receipt.contract_address.unwrap(); + let authorization = Authorization { + chain_id: 31337, + address: contract, + nonce: OptionalNonce::new(Some(provider.get_transaction_count(from).await.unwrap())), + }; + let signature = wallets[0].sign_hash_sync(&authorization.signature_hash()).unwrap(); + let authorization = authorization.into_signed(signature); + + let log_data = bytes!("11112222"); + let mut tx = TxEip7702 { + max_fee_per_gas: eip1559_est.max_fee_per_gas, + max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas, + gas_limit: 100000, + chain_id: 31337, + to: TxKind::Call(from), + input: bytes!("11112222"), + authorization_list: vec![authorization], + ..Default::default() + }; + let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); + + let tx = tx.into_signed(signature); + let mut encoded = Vec::new(); + tx.tx().encode_with_signature(tx.signature(), &mut encoded, false); + + let receipt = + provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap(); + let log = &receipt.inner.inner.logs()[0]; + // assert that log was from EOA which signed authorization + assert_eq!(log.address(), from); + assert_eq!(log.topics().len(), 0); + assert_eq!(log.data().data, log_data); +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 83c235f5c..b27b361b2 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -4,22 +4,22 @@ use crate::{ abi::{Greeter, ERC721}, utils::{http_provider, http_provider_with_signer}, }; -use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{address, Address, Bytes, TxKind, U256}; +use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{address, bytes, Address, Bytes, TxHash, TxKind, U256}; use alloy_provider::Provider; use alloy_rpc_types::{ + anvil::Forking, request::{TransactionInput, TransactionRequest}, BlockId, BlockNumberOrTag, }; use alloy_serde::WithOtherFields; use alloy_signer_local::PrivateKeySigner; use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; -use anvil_core::types::Forking; use foundry_common::provider::get_http_provider; use foundry_config::Config; use foundry_test_utils::rpc::{self, next_http_rpc_endpoint}; use futures::StreamExt; -use std::{sync::Arc, time::Duration}; +use std::{sync::Arc, thread::sleep, time::Duration}; const BLOCK_NUMBER: u64 = 14_608_400u64; const DEAD_BALANCE_AT_BLOCK_NUMBER: u128 = 12_556_069_338_441_120_059_867u128; @@ -1191,7 +1191,7 @@ async fn test_fork_execution_reverted() { .call( WithOtherFields::new(TransactionRequest { to: Some(TxKind::from(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377"))), - input: TransactionInput::new("0x8f283b3c".as_bytes().into()), + input: TransactionInput::new(bytes!("8f283b3c")), ..Default::default() }), Some(target.into()), @@ -1203,3 +1203,143 @@ async fn test_fork_execution_reverted() { let err = resp.unwrap_err(); assert!(err.to_string().contains("execution reverted")); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_immutable_fork_transaction_hash() { + use std::str::FromStr; + + // Fork to a block with a specific transaction + let fork_tx_hash = + TxHash::from_str("39d64ebf9eb3f07ede37f8681bc3b61928817276c4c4680b6ef9eac9f88b6786") + .unwrap(); + let (api, _) = spawn( + fork_config() + .with_blocktime(Some(Duration::from_millis(500))) + .with_fork_transaction_hash(Some(fork_tx_hash)) + .with_eth_rpc_url(Some("https://rpc.immutable.com".to_string())), + ) + .await; + + let fork_block_number = 8521008; + + // Make sure the fork starts from previous block + let mut block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, fork_block_number - 1); + + // Wait for fork to pass the target block + while block_number < fork_block_number { + sleep(Duration::from_millis(250)); + block_number = api.block_number().unwrap().to::(); + } + + let block = api + .block_by_number(BlockNumberOrTag::Number(fork_block_number - 1)) + .await + .unwrap() + .unwrap(); + assert_eq!(block.transactions.len(), 14); + let block = api + .block_by_number_full(BlockNumberOrTag::Number(fork_block_number)) + .await + .unwrap() + .unwrap(); + assert_eq!(block.transactions.len(), 3); + + // Validate the transactions preceding the target transaction exist + let expected_transactions = [ + TxHash::from_str("1bfe33136edc3d26bd01ce75c8f5ae14fffe8b142d30395cb4b6d3dc3043f400") + .unwrap(), + TxHash::from_str("8c0ce5fb9ec2c8e03f7fcc69c7786393c691ce43b58a06d74d6733679308fc01") + .unwrap(), + fork_tx_hash, + ]; + for expected in [ + (expected_transactions[0], address!("8C1aB379E7263d37049505626D2F975288F5dF12")), + (expected_transactions[1], address!("df918d9D02d5C7Df6825a7046dBF3D10F705Aa76")), + (expected_transactions[2], address!("5Be88952ce249024613e0961eB437f5E9424A90c")), + ] { + let tx = api.backend.mined_transaction_by_hash(expected.0).unwrap(); + assert_eq!(tx.inner.from, expected.1); + } + + // Validate the order of transactions in the new block + for expected in [ + (expected_transactions[0], 0), + (expected_transactions[1], 1), + (expected_transactions[2], 2), + ] { + let tx = api + .backend + .mined_block_by_number(BlockNumberOrTag::Number(fork_block_number)) + .and_then(|b| b.header.hash) + .and_then(|hash| { + api.backend.mined_transaction_by_block_hash_and_index(hash, expected.1.into()) + }) + .unwrap(); + assert_eq!(tx.inner.hash.to_string(), expected.0.to_string()); + } +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_query_at_fork_block() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + let info = api.anvil_node_info().await.unwrap(); + let number = info.fork_config.fork_block_number.unwrap(); + assert_eq!(number, BLOCK_NUMBER); + + let address = Address::random(); + + let balance = provider.get_balance(address).await.unwrap(); + api.evm_mine(None).await.unwrap(); + api.anvil_set_balance(address, balance + U256::from(1)).await.unwrap(); + + let balance_before = + provider.get_balance(address).block_id(BlockId::number(number)).await.unwrap(); + + assert_eq!(balance_before, balance); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_reset_dev_account_nonce() { + let config: NodeConfig = fork_config(); + let address = config.genesis_accounts[0].address(); + let (api, handle) = spawn(config).await; + let provider = handle.http_provider(); + let info = api.anvil_node_info().await.unwrap(); + let number = info.fork_config.fork_block_number.unwrap(); + assert_eq!(number, BLOCK_NUMBER); + + let nonce_before = provider.get_transaction_count(address).await.unwrap(); + + // Reset to older block with other nonce + api.anvil_reset(Some(Forking { + json_rpc_url: None, + block_number: Some(BLOCK_NUMBER - 1_000_000), + })) + .await + .unwrap(); + + let nonce_after = provider.get_transaction_count(address).await.unwrap(); + + assert!(nonce_before > nonce_after); + + let receipt = provider + .send_transaction(WithOtherFields::new( + TransactionRequest::default() + .from(address) + .to(address) + .nonce(nonce_after) + .gas_limit(21000u128), + )) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert!(receipt.status()); +} diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index af28983f1..ae9c9c201 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -191,5 +191,13 @@ async fn test_can_use_fee_history() { let receipt = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.inner.inner.is_success()); + + let fee_history_after = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); + let latest_fee_history_fee = fee_history_after.base_fee_per_gas.first().unwrap(); + let latest_block = + provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + assert_eq!(latest_block.header.base_fee_per_gas.unwrap(), *latest_fee_history_fee); + assert_eq!(latest_fee_history_fee, next_base_fee); } } diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index 559593b72..a79f67bb1 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -4,6 +4,7 @@ mod anvil; mod anvil_api; mod api; mod eip4844; +mod eip7702; mod fork; mod gas; mod genesis; diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 3406e6865..c355d11e8 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -3,7 +3,7 @@ use crate::utils::http_provider_with_signer; use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{b256, U128, U256}; +use alloy_primitives::{b256, U256}; use alloy_provider::Provider; use alloy_rpc_types::{optimism::OptimismTransactionFields, TransactionRequest}; use alloy_serde::WithOtherFields; @@ -29,7 +29,7 @@ async fn test_deposits_not_supported_if_optimism_disabled() { source_hash: Some(b256!( "0000000000000000000000000000000000000000000000000000000000000000" )), - mint: Some(U128::from(0)), + mint: Some(0), is_system_tx: Some(true), } .into(), @@ -67,7 +67,7 @@ async fn test_send_value_deposit_transaction() { source_hash: Some(b256!( "0000000000000000000000000000000000000000000000000000000000000000" )), - mint: Some(U128::from(0)), + mint: Some(0), is_system_tx: Some(true), } .into(), @@ -119,7 +119,7 @@ async fn test_send_value_raw_deposit_transaction() { source_hash: Some(b256!( "0000000000000000000000000000000000000000000000000000000000000000" )), - mint: Some(U128::from(0)), + mint: Some(0), is_system_tx: Some(true), } .into(), diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index dc0f297fd..d0536a032 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -3,15 +3,13 @@ use crate::abi::MulticallContract; use alloy_primitives::{address, Address, Bytes, U256}; use alloy_provider::Provider; -use alloy_rpc_types::{BlockNumberOrTag, BlockTransactions, TransactionRequest}; -use alloy_serde::WithOtherFields; -use alloy_sol_types::{sol, SolCall, SolError}; -use anvil::{ - eth::otterscan::types::{ - OtsInternalOperation, OtsInternalOperationType, OtsTrace, OtsTraceType, - }, - spawn, Hardfork, NodeConfig, +use alloy_rpc_types::{ + trace::otterscan::{InternalOperation, OperationType, TraceEntry}, + BlockNumberOrTag, TransactionRequest, }; +use alloy_serde::WithOtherFields; +use alloy_sol_types::{sol, SolCall, SolError, SolValue}; +use anvil::{spawn, Hardfork, NodeConfig}; use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] @@ -50,8 +48,8 @@ async fn ots_get_internal_operations_contract_deploy() { let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!( res, - [OtsInternalOperation { - r#type: OtsInternalOperationType::Create, + [InternalOperation { + r#type: OperationType::OpCreate, from: sender, to: contract_receipt.contract_address.unwrap(), value: U256::from(0) @@ -78,12 +76,7 @@ async fn ots_get_internal_operations_contract_transfer() { let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, - [OtsInternalOperation { - r#type: OtsInternalOperationType::Transfer, - from, - to, - value: amount - }], + [InternalOperation { r#type: OperationType::OpTransfer, from, to, value: amount }], ); } @@ -114,8 +107,8 @@ async fn ots_get_internal_operations_contract_create2() { let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, - [OtsInternalOperation { - r#type: OtsInternalOperationType::Create2, + [InternalOperation { + r#type: OperationType::OpCreate2, from: address!("4e59b44847b379578588920cA78FbF26c0B4956C"), to: address!("347bcdad821abc09b8c275881b368de36476b62c"), value: U256::from(0), @@ -166,8 +159,8 @@ async fn ots_get_internal_operations_contract_selfdestruct(hardfork: Hardfork) { let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, - [OtsInternalOperation { - r#type: OtsInternalOperationType::SelfDestruct, + [InternalOperation { + r#type: OperationType::OpSelfDestruct, from: contract_address, to: expected_to, value: expected_value, @@ -243,50 +236,50 @@ async fn test_call_ots_trace_transaction() { let res = api.ots_trace_transaction(receipt.transaction_hash).await.unwrap(); let expected = vec![ - OtsTrace { - r#type: OtsTraceType::Call, + TraceEntry { + r#type: "CALL".to_string(), depth: 0, from: sender, to: contract_address, value: U256::from(1337), input: Contract::runCall::SELECTOR.into(), - output: None, + output: Bytes::new(), }, - OtsTrace { - r#type: OtsTraceType::StaticCall, + TraceEntry { + r#type: "STATICCALL".to_string(), depth: 1, from: contract_address, to: contract_address, value: U256::ZERO, input: Contract::do_staticcallCall::SELECTOR.into(), - output: None, + output: true.abi_encode().into(), }, - OtsTrace { - r#type: OtsTraceType::Call, + TraceEntry { + r#type: "CALL".to_string(), depth: 1, from: contract_address, to: contract_address, value: U256::ZERO, input: Contract::do_callCall::SELECTOR.into(), - output: None, + output: Bytes::new(), }, - OtsTrace { - r#type: OtsTraceType::Call, + TraceEntry { + r#type: "CALL".to_string(), depth: 2, from: contract_address, to: sender, value: U256::from(1337), input: Bytes::new(), - output: None, + output: Bytes::new(), }, - OtsTrace { - r#type: OtsTraceType::DelegateCall, + TraceEntry { + r#type: "DELEGATECALL".to_string(), depth: 2, from: contract_address, to: contract_address, value: U256::ZERO, input: Contract::do_delegatecallCall::SELECTOR.into(), - output: None, + output: Bytes::new(), }, ]; assert_eq!(res, expected); @@ -338,13 +331,11 @@ async fn ots_get_block_details() { let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); let tx = WithOtherFields::new(tx); - let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let result = api.ots_get_block_details(1.into()).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - let hash = result.block.block.transactions.hashes().next().unwrap(); - assert_eq!(*hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] @@ -360,12 +351,6 @@ async fn ots_get_block_details_by_hash() { let result = api.ots_get_block_details_by_hash(block_hash).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - let hash = match result.block.block.transactions { - BlockTransactions::Full(txs) => txs[0].hash, - BlockTransactions::Hashes(hashes) => hashes[0], - BlockTransactions::Uncle => unreachable!(), - }; - assert_eq!(hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] @@ -399,7 +384,7 @@ async fn ots_get_block_transactions() { result.receipts.iter().enumerate().for_each(|(i, receipt)| { let expected = hashes.pop_front(); - assert_eq!(expected, Some(receipt.transaction_hash)); + assert_eq!(expected, Some(receipt.receipt.transaction_hash)); assert_eq!(expected, result.fullblock.block.transactions.hashes().nth(i).copied()); }); } diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 1c4927300..7f2846d5c 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,14 +1,24 @@ -use crate::{fork::fork_config, utils::http_provider_with_signer}; +use crate::{ + abi::{MulticallContract, SimpleStorage}, + fork::fork_config, + utils::http_provider_with_signer, +}; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{hex, Address, Bytes, U256}; use alloy_provider::{ ext::{DebugApi, TraceApi}, Provider, }; -use alloy_rpc_types::{BlockNumberOrTag, TransactionRequest}; -use alloy_rpc_types_trace::{ - geth::{GethDebugTracingCallOptions, GethTrace}, - parity::{Action, LocalizedTransactionTrace}, +use alloy_rpc_types::{ + trace::{ + filter::{TraceFilter, TraceFilterMode}, + geth::{ + CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, + }, + parity::{Action, LocalizedTransactionTrace}, + }, + BlockNumberOrTag, TransactionRequest, }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; @@ -136,6 +146,117 @@ async fn test_transfer_debug_trace_call() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_call_tracer_debug_trace_call() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let wallets = handle.dev_wallets().collect::>(); + let deployer: EthereumWallet = wallets[0].clone().into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); + + let multicall_contract = MulticallContract::deploy(&provider).await.unwrap(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![MulticallContract::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + // calling SimpleStorage contract through Multicall should result in an internal call + let internal_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*multicall_contract.address()) + .with_input(internal_call_tx_calldata); + + let internal_call_tx_traces = handle + .http_provider() + .debug_trace_call( + internal_call_tx.clone(), + BlockNumberOrTag::Latest, + GethDebugTracingCallOptions::default().with_tracing_options( + GethDebugTracingOptions::default() + .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) + .with_call_config(CallConfig::default().with_log()), + ), + ) + .await + .unwrap(); + + match internal_call_tx_traces { + GethTrace::CallTracer(call_frame) => { + assert!(call_frame.calls.len() == 1); + assert!( + call_frame.calls.first().unwrap().to.unwrap() == *simple_storage_contract.address() + ); + assert!(call_frame.calls.first().unwrap().logs.len() == 1); + } + _ => { + unreachable!() + } + } + + // only_top_call option - should not return any internal calls + let internal_call_only_top_call_tx_traces = handle + .http_provider() + .debug_trace_call( + internal_call_tx.clone(), + BlockNumberOrTag::Latest, + GethDebugTracingCallOptions::default().with_tracing_options( + GethDebugTracingOptions::default() + .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) + .with_call_config(CallConfig::default().with_log().only_top_call()), + ), + ) + .await + .unwrap(); + + match internal_call_only_top_call_tx_traces { + GethTrace::CallTracer(call_frame) => { + assert!(call_frame.calls.is_empty()); + } + _ => { + unreachable!() + } + } + + // directly calling the SimpleStorage contract should not result in any internal calls + let direct_call_tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*simple_storage_contract.address()) + .with_input(set_value_calldata.to_owned()); + + let direct_call_tx_traces = handle + .http_provider() + .debug_trace_call( + direct_call_tx, + BlockNumberOrTag::Latest, + GethDebugTracingCallOptions::default().with_tracing_options( + GethDebugTracingOptions::default() + .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) + .with_call_config(CallConfig::default().with_log()), + ), + ) + .await + .unwrap(); + + match direct_call_tx_traces { + GethTrace::CallTracer(call_frame) => { + assert!(call_frame.calls.is_empty()); + assert!(call_frame.to.unwrap() == *simple_storage_contract.address()); + assert!(call_frame.logs.len() == 1); + } + _ => { + unreachable!() + } + } +} + // #[tokio::test(flavor = "multi_thread")] async fn test_trace_address_fork() { @@ -596,3 +717,144 @@ async fn test_trace_address_fork2() { } }) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_trace_filter() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.ws_provider(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let from_two = accounts[2].address(); + let to_two = accounts[3].address(); + + // Test default block ranges. + // From will be earliest, to will be best/latest + let tracer = TraceFilter { + from_block: None, + to_block: None, + from_address: vec![], + to_address: vec![], + mode: TraceFilterMode::Intersection, + after: None, + count: None, + }; + + for i in 0..=5 { + let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + let traces = api.trace_filter(tracer).await.unwrap(); + assert_eq!(traces.len(), 5); + + // Test filtering by address + let tracer = TraceFilter { + from_block: Some(provider.get_block_number().await.unwrap()), + to_block: None, + from_address: vec![from_two], + to_address: vec![to_two], + mode: TraceFilterMode::Intersection, + after: None, + count: None, + }; + + for i in 0..=5 { + let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + + let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + let traces = api.trace_filter(tracer).await.unwrap(); + assert_eq!(traces.len(), 5); + + // Test for the following actions: + // Create (deploy the contract) + // Call (goodbye function) + // SelfDestruct (side-effect of goodbye) + let contract_addr = + SuicideContract::deploy_builder(provider.clone()).from(from).deploy().await.unwrap(); + let contract = SuicideContract::new(contract_addr, provider.clone()); + + // Test TraceActions + let tracer = TraceFilter { + from_block: Some(provider.get_block_number().await.unwrap()), + to_block: None, + from_address: vec![from, contract_addr], + to_address: vec![], // Leave as 0 address + mode: TraceFilterMode::Union, + after: None, + count: None, + }; + + // Execute call + let call = contract.goodbye().from(from); + let call = call.send().await.unwrap(); + call.get_receipt().await.unwrap(); + + // Mine transactions to filter against + for i in 0..=5 { + let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + let traces = api.trace_filter(tracer).await.unwrap(); + assert_eq!(traces.len(), 3); + + // Test Range Error + let latest = provider.get_block_number().await.unwrap(); + let tracer = TraceFilter { + from_block: Some(latest), + to_block: Some(latest + 301), + from_address: vec![], + to_address: vec![], + mode: TraceFilterMode::Union, + after: None, + count: None, + }; + + let traces = api.trace_filter(tracer).await; + assert!(traces.is_err()); + + // Test invalid block range + let latest = provider.get_block_number().await.unwrap(); + let tracer = TraceFilter { + from_block: Some(latest + 10), + to_block: Some(latest), + from_address: vec![], + to_address: vec![], + mode: TraceFilterMode::Union, + after: None, + count: None, + }; + + let traces = api.trace_filter(tracer).await; + assert!(traces.is_err()); + + // Test after and count + let tracer = TraceFilter { + from_block: Some(provider.get_block_number().await.unwrap()), + to_block: None, + from_address: vec![], + to_address: vec![], + mode: TraceFilterMode::Union, + after: Some(3), + count: Some(5), + }; + + for i in 0..=10 { + let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + let traces = api.trace_filter(tracer).await.unwrap(); + assert_eq!(traces.len(), 5); +} diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 10b996854..5f9abe80b 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -879,7 +879,7 @@ async fn test_tx_receipt() { let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - // `to` field is none if it's a contract creation transaction: https://eth.wiki/json-rpc/API#eth_getTransactionReceipt + // `to` field is none if it's a contract creation transaction: https://ethereum.org/developers/docs/apis/json-rpc/#eth_gettransactionreceipt assert!(tx.to.is_none()); assert!(tx.contract_address.is_some()); } diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 525c2c3f7..1d899bbba 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -54,7 +54,6 @@ chrono.workspace = true evm-disassembler.workspace = true eyre.workspace = true futures.workspace = true -hex.workspace = true rand.workspace = true rayon.workspace = true serde_json.workspace = true @@ -69,7 +68,7 @@ foundry-cli.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" -comfy-table = "7" +comfy-table.workspace = true dunce.workspace = true indicatif = "0.17" itertools.workspace = true @@ -97,6 +96,7 @@ openssl = ["foundry-cli/openssl"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["dep:tikv-jemallocator"] aws-kms = ["foundry-wallets/aws-kms", "dep:aws-sdk-kms"] +isolate-by-default = ["foundry-config/isolate-by-default"] [[bench]] name = "vanity" diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 0499f5e7b..65ec11d6e 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -43,6 +43,9 @@ pub struct CallArgs { #[arg(long, requires = "trace")] debug: bool, + #[arg(long, requires = "trace")] + decode_internal: bool, + /// Labels to apply to the traces; format: `address:label`. /// Can only be used with `--trace`. #[arg(long, requires = "trace")] @@ -106,6 +109,7 @@ impl CallArgs { trace, evm_version, debug, + decode_internal, labels, data, } = self; @@ -158,8 +162,14 @@ impl CallArgs { config.fork_block_number = Some(block_number); } - let (env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; - let mut executor = TracingExecutor::new(env, fork, evm_version, debug); + let (mut env, fork, chain) = + TracingExecutor::get_fork_material(&config, evm_opts).await?; + + // modify settings that usually set in eth_call + env.cfg.disable_block_gas_limit = true; + env.block.gas_limit = U256::MAX; + + let mut executor = TracingExecutor::new(env, fork, evm_version, debug, decode_internal); let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); @@ -175,7 +185,7 @@ impl CallArgs { ), }; - handle_traces(trace, &config, chain, labels, debug).await?; + handle_traces(trace, &config, chain, labels, debug, decode_internal).await?; return Ok(()); } @@ -189,7 +199,7 @@ impl CallArgs { #[cfg(test)] mod tests { use super::*; - use alloy_primitives::Address; + use alloy_primitives::{hex, Address}; #[test] fn can_parse_call_data() { diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index 8c3b12dad..0f751fc89 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_primitives::{hex, keccak256, Address, B256, U256}; use clap::Parser; use eyre::{Result, WrapErr}; use rand::{rngs::StdRng, RngCore, SeedableRng}; diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index 7d92ab935..c51bad8cc 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -1,7 +1,7 @@ use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; use alloy_json_abi::Event; use alloy_network::AnyNetwork; -use alloy_primitives::{Address, B256}; +use alloy_primitives::{hex::FromHex, Address, B256}; use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use cast::Cast; use clap::Parser; @@ -9,7 +9,6 @@ use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils}; use foundry_common::ens::NameOrAddress; use foundry_config::Config; -use hex::FromHex; use itertools::Itertools; use std::{io, str::FromStr}; diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs index db8dbf0fe..4a343af6d 100644 --- a/crates/cast/bin/cmd/mktx.rs +++ b/crates/cast/bin/cmd/mktx.rs @@ -1,5 +1,6 @@ use crate::tx::{self, CastTxBuilder}; use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; +use alloy_primitives::hex; use alloy_signer::Signer; use clap::Parser; use eyre::Result; @@ -9,7 +10,7 @@ use foundry_cli::{ }; use foundry_common::ens::NameOrAddress; use foundry_config::Config; -use std::str::FromStr; +use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast mktx`. #[derive(Debug, Parser)] @@ -32,6 +33,16 @@ pub struct MakeTxArgs { #[command(flatten)] tx: TransactionOpts, + /// The path of blob data to be sent. + #[arg( + long, + value_name = "BLOB_DATA_PATH", + conflicts_with = "legacy", + requires = "blob", + help_heading = "Transaction options" + )] + path: Option, + #[command(flatten)] eth: EthereumOpts, } @@ -54,7 +65,9 @@ pub enum MakeTxSubcommands { impl MakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, command, tx, eth } = self; + let Self { to, mut sig, mut args, command, tx, path, eth } = self; + + let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; let code = if let Some(MakeTxSubcommands::Create { code, @@ -87,6 +100,7 @@ impl MakeTxArgs { .with_tx_kind(tx_kind) .with_code_sig_and_args(code, sig, args) .await? + .with_blob_data(blob_data)? .build(from) .await?; diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index c830f5ab1..b7f31cfc7 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -27,6 +27,10 @@ pub struct RunArgs { #[arg(long, short)] debug: bool, + /// Whether to identify internal functions in traces. + #[arg(long)] + decode_internal: bool, + /// Print out opcode traces. #[arg(long, short)] trace_printer: bool, @@ -142,7 +146,8 @@ impl RunArgs { } } - let mut executor = TracingExecutor::new(env.clone(), fork, evm_version, self.debug); + let mut executor = + TracingExecutor::new(env.clone(), fork, evm_version, self.debug, self.decode_internal); let mut env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); @@ -220,7 +225,7 @@ impl RunArgs { } }; - handle_traces(result, &config, chain, self.label, self.debug).await?; + handle_traces(result, &config, chain, self.label, self.debug, self.decode_internal).await?; Ok(()) } diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 55094346f..cdafbc8a3 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -57,7 +57,13 @@ pub struct SendTxArgs { eth: EthereumOpts, /// The path of blob data to be sent. - #[arg(long, value_name = "BLOB_DATA_PATH", conflicts_with = "legacy", requires = "blob")] + #[arg( + long, + value_name = "BLOB_DATA_PATH", + conflicts_with = "legacy", + requires = "blob", + help_heading = "Transaction options" + )] path: Option, } diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 2b50630c3..64f048b2c 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,5 +1,5 @@ use alloy_dyn_abi::TypedData; -use alloy_primitives::{Address, Signature, B256}; +use alloy_primitives::{hex, Address, Signature, B256}; use alloy_signer::Signer; use alloy_signer_local::{ coins_bip39::{English, Entropy, Mnemonic}, diff --git a/crates/cast/bin/cmd/wallet/vanity.rs b/crates/cast/bin/cmd/wallet/vanity.rs index 1e94b5f9a..5b597b3f0 100644 --- a/crates/cast/bin/cmd/wallet/vanity.rs +++ b/crates/cast/bin/cmd/wallet/vanity.rs @@ -1,8 +1,9 @@ -use alloy_primitives::Address; +use alloy_primitives::{hex, Address}; use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; use alloy_signer_local::PrivateKeySigner; -use clap::{builder::TypedValueParser, Parser}; +use clap::Parser; use eyre::Result; +use itertools::Either; use rayon::iter::{self, ParallelIterator}; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -18,17 +19,12 @@ pub type GeneratedWallet = (SigningKey, Address); /// CLI arguments for `cast wallet vanity`. #[derive(Clone, Debug, Parser)] pub struct VanityArgs { - /// Prefix for the vanity address. - #[arg( - long, - required_unless_present = "ends_with", - value_parser = HexAddressValidator, - value_name = "HEX" - )] + /// Prefix regex pattern or hex string. + #[arg(long, value_name = "PATTERN", required_unless_present = "ends_with")] pub starts_with: Option, - /// Suffix for the vanity address. - #[arg(long, value_parser = HexAddressValidator, value_name = "HEX")] + /// Suffix regex pattern or hex string. + #[arg(long, value_name = "PATTERN")] pub ends_with: Option, // 2^64-1 is max possible nonce per [eip-2681](https://eips.ethereum.org/EIPS/eip-2681). @@ -74,24 +70,22 @@ impl WalletData { impl VanityArgs { pub fn run(self) -> Result { let Self { starts_with, ends_with, nonce, save_path } = self; + let mut left_exact_hex = None; let mut left_regex = None; - let mut right_exact_hex = None; - let mut right_regex = None; - if let Some(prefix) = starts_with { - if let Ok(decoded) = hex::decode(&prefix) { - left_exact_hex = Some(decoded) - } else { - left_regex = Some(Regex::new(&format!(r"^{prefix}"))?); + match parse_pattern(&prefix, true)? { + Either::Left(left) => left_exact_hex = Some(left), + Either::Right(re) => left_regex = Some(re), } } + let mut right_exact_hex = None; + let mut right_regex = None; if let Some(suffix) = ends_with { - if let Ok(decoded) = hex::decode(&suffix) { - right_exact_hex = Some(decoded) - } else { - right_regex = Some(Regex::new(&format!(r"{suffix}$"))?); + match parse_pattern(&suffix, false)? { + Either::Left(right) => right_exact_hex = Some(right), + Either::Right(re) => right_regex = Some(re), } } @@ -151,8 +145,8 @@ impl VanityArgs { } println!( - "Successfully found vanity address in {} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}", - timer.elapsed().as_secs(), + "Successfully found vanity address in {:.3} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}", + timer.elapsed().as_secs_f64(), if nonce.is_some() { "\nContract address: " } else { "" }, if nonce.is_some() { wallet.address().create(nonce.unwrap()).to_checksum(None) @@ -331,29 +325,15 @@ impl VanityMatcher for RegexMatcher { } } -/// Parse 40 byte addresses -#[derive(Clone, Copy, Debug, Default)] -pub struct HexAddressValidator; - -impl TypedValueParser for HexAddressValidator { - type Value = String; - - fn parse_ref( - &self, - _cmd: &clap::Command, - _arg: Option<&clap::Arg>, - value: &std::ffi::OsStr, - ) -> Result { - if value.len() > 40 { - return Err(clap::Error::raw( - clap::error::ErrorKind::InvalidValue, - "vanity patterns length exceeded. cannot be more than 40 characters", - )) +fn parse_pattern(pattern: &str, is_start: bool) -> Result, Regex>> { + if let Ok(decoded) = hex::decode(pattern) { + if decoded.len() > 20 { + return Err(eyre::eyre!("Hex pattern must be less than 20 bytes")); } - let value = value.to_str().ok_or_else(|| { - clap::Error::raw(clap::error::ErrorKind::InvalidUtf8, "address must be valid utf8") - })?; - Ok(value.to_string()) + Ok(Either::Left(decoded)) + } else { + let (prefix, suffix) = if is_start { ("^", "") } else { ("", "$") }; + Ok(Either::Right(Regex::new(&format!("{prefix}{pattern}{suffix}"))?)) } } diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 0f6673d4f..ff4f799cd 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate tracing; -use alloy_primitives::{keccak256, Address, B256}; +use alloy_primitives::{hex, keccak256, Address, B256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use cast::{Cast, SimpleCast}; @@ -567,6 +567,10 @@ async fn main() -> Result<()> { println!("{}", serde_json::to_string_pretty(&tx)?); } + CastSubcommand::DecodeEof { eof } => { + let eof = stdin::unwrap_line(eof)?; + println!("{}", SimpleCast::decode_eof(&eof)?); + } }; Ok(()) } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs index decd60374..5b3c09c8a 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/opts.rs @@ -259,18 +259,28 @@ pub enum CastSubcommand { }, /// RLP encodes hex data, or an array of hex data. + /// + /// Accepts a hex-encoded string, or an array of hex-encoded strings. + /// Can be arbitrarily recursive. + /// + /// Examples: + /// - `cast to-rlp "[]"` -> `0xc0` + /// - `cast to-rlp "0x22"` -> `0x22` + /// - `cast to-rlp "[\"0x61\"]"` -> `0xc161` + /// - `cast to-rlp "[\"0xf1\", \"f2\"]"` -> `0xc481f181f2` #[command(visible_aliases = &["--to-rlp"])] ToRlp { /// The value to convert. + /// + /// This is a hex-encoded string, or an array of hex-encoded strings. + /// Can be arbitrarily recursive. value: Option, }, - /// Decodes RLP encoded data. - /// - /// Input must be hexadecimal. + /// Decodes RLP hex-encoded data. #[command(visible_aliases = &["--from-rlp"])] FromRlp { - /// The value to convert. + /// The RLP hex-encoded data. value: Option, }, @@ -895,7 +905,7 @@ pub enum CastSubcommand { }, /// Decodes a raw signed EIP 2718 typed transaction - #[command(visible_alias = "dt")] + #[command(visible_aliases = &["dt", "decode-tx"])] DecodeTransaction { tx: Option }, /// Extracts function selectors and arguments from bytecode @@ -908,6 +918,10 @@ pub enum CastSubcommand { #[arg(long, short)] resolve: bool, }, + + /// Decodes EOF container bytes + #[command()] + DecodeEof { eof: Option }, } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs index c0f1e1a97..9a731187a 100644 --- a/crates/cast/bin/tx.rs +++ b/crates/cast/bin/tx.rs @@ -1,9 +1,9 @@ use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_json_abi::Function; use alloy_network::{AnyNetwork, TransactionBuilder}; -use alloy_primitives::{Address, TxKind}; +use alloy_primitives::{hex, Address, Bytes, TxKind}; use alloy_provider::Provider; -use alloy_rpc_types::TransactionRequest; +use alloy_rpc_types::{TransactionInput, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_transport::Transport; use eyre::Result; @@ -94,6 +94,7 @@ where let chain = utils::get_chain(config.chain, &provider).await?; let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); + let legacy = tx_opts.legacy || chain.is_legacy(); if let Some(gas_limit) = tx_opts.gas_limit { tx.set_gas_limit(gas_limit.to()); @@ -104,14 +105,14 @@ where } if let Some(gas_price) = tx_opts.gas_price { - if tx_opts.legacy { + if legacy { tx.set_gas_price(gas_price.to()); } else { tx.set_max_fee_per_gas(gas_price.to()); } } - if !tx_opts.legacy { + if !legacy { if let Some(priority_fee) = tx_opts.priority_gas_price { tx.set_max_priority_fee_per_gas(priority_fee.to()); } @@ -128,7 +129,7 @@ where Ok(Self { provider, tx, - legacy: tx_opts.legacy || chain.is_legacy(), + legacy, blob: tx_opts.blob, chain, etherscan_api_key, @@ -232,7 +233,11 @@ where let from = from.into().resolve(&self.provider).await?; self.tx.set_kind(self.state.kind); - self.tx.set_input(self.state.input); + + // we set both fields to the same value because some nodes only accept the legacy `data` field: + let input = Bytes::from(self.state.input); + self.tx.input = TransactionInput { input: Some(input.clone()), data: Some(input) }; + self.tx.set_from(from); self.tx.set_chain_id(self.chain.id()); diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 4934d8dd3..46610d630 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -6,6 +6,7 @@ use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; use alloy_json_abi::Function; use alloy_network::AnyNetwork; use alloy_primitives::{ + hex, utils::{keccak256, ParseUnits, Unit}, Address, Keccak256, TxHash, TxKind, B256, I256, U256, }; @@ -27,12 +28,13 @@ use foundry_common::{ abi::{encode_function_args, get_func}, compile::etherscan_project, fmt::*, - fs, TransactionReceiptWithRevertReason, + fs, get_pretty_tx_receipt_attr, TransactionReceiptWithRevertReason, }; use foundry_compilers::flatten::Flattener; use foundry_config::Chain; use futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; +use revm::primitives::Eof; use std::{ borrow::Cow, io, @@ -1354,7 +1356,7 @@ impl SimpleCast { /// assert_eq!(Cast::to_rlp("[]").unwrap(), "0xc0".to_string()); /// assert_eq!(Cast::to_rlp("0x22").unwrap(), "0x22".to_string()); /// assert_eq!(Cast::to_rlp("[\"0x61\"]",).unwrap(), "0xc161".to_string()); - /// assert_eq!(Cast::to_rlp("[\"0xf1\",\"f2\"]").unwrap(), "0xc481f181f2".to_string()); + /// assert_eq!(Cast::to_rlp("[\"0xf1\", \"f2\"]").unwrap(), "0xc481f181f2".to_string()); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_rlp(value: &str) -> Result { @@ -1477,7 +1479,7 @@ impl SimpleCast { /// /// ``` /// use cast::SimpleCast as Cast; - /// use hex; + /// use alloy_primitives::hex; /// /// // Passing `input = false` will decode the data as the output type. /// // The input data types and the full function sig are ignored, i.e. @@ -1520,6 +1522,7 @@ impl SimpleCast { /// /// ``` /// use cast::SimpleCast as Cast; + /// use alloy_primitives::hex; /// /// // Passing `input = false` will decode the data as the output type. /// // The input data types and the full function sig are ignored, i.e. @@ -1988,6 +1991,24 @@ impl SimpleCast { let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; Ok(tx) } + + /// Decodes EOF container bytes + /// Pretty prints the decoded EOF container contents + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let eof = "0xef0001010004020001005604002000008000046080806040526004361015e100035f80fd5f3560e01c63773d45e01415e1ffee6040600319360112e10028600435906024358201809211e100066020918152f3634e487b7160e01b5f52601160045260245ffd5f80fd0000000000000000000000000124189fc71496f8660db5189f296055ed757632"; + /// let decoded = Cast::decode_eof(&eof)?; + /// println!("{}", decoded); + /// # Ok::<(), eyre::Report>(()) + pub fn decode_eof(eof: &str) -> Result { + let eof_hex = hex::decode(eof)?; + let eof = Eof::decode(eof_hex.into())?; + Ok(pretty_eof(&eof)?) + } } fn strip_0x(s: &str) -> &str { diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index cab852ab3..ad1787438 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -1,4 +1,6 @@ +use alloy_primitives::{hex, U256}; use alloy_rlp::{Buf, Decodable, Encodable, Header}; +use eyre::Context; use serde_json::Value; use std::fmt; @@ -48,11 +50,12 @@ impl Item { return match value { Value::Null => Ok(Self::Data(vec![])), Value::Bool(_) => { - eyre::bail!("RLP input should not contain booleans") + eyre::bail!("RLP input can not contain booleans") } - // If a value is passed without quotes we cast it to string - Value::Number(n) => Ok(Self::value_to_item(&Value::String(n.to_string()))?), - Value::String(s) => Ok(Self::Data(hex::decode(s).expect("Could not decode hex"))), + Value::Number(n) => { + Ok(Self::Data(n.to_string().parse::()?.to_be_bytes_trimmed_vec())) + } + Value::String(s) => Ok(Self::Data(hex::decode(s).wrap_err("Could not decode hex")?)), Value::Array(values) => values.iter().map(Self::value_to_item).collect(), Value::Object(_) => { eyre::bail!("RLP input can not contain objects") diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 7d7e33881..8349f6d3e 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -4,6 +4,7 @@ use alloy_primitives::{address, b256, Address, B256}; use foundry_test_utils::{ casttest, rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}, + str, util::OutputExt, }; use std::{fs, io::Write, path::Path, str::FromStr}; @@ -103,9 +104,10 @@ casttest!(wallet_sign_message_hex_data, |_prj, cmd| { "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x23a42ca5616ee730ff3735890c32fc7b9491a9f633faca9434797f2c845f5abf4d9ba23bd7edb8577acebaa3644dc5a4995296db420522bb40060f1693c33c9b1c"); + ]).assert_success().stdout_eq(str![[r#" +0x23a42ca5616ee730ff3735890c32fc7b9491a9f633faca9434797f2c845f5abf4d9ba23bd7edb8577acebaa3644dc5a4995296db420522bb40060f1693c33c9b1c + +"#]]); }); // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON string @@ -117,9 +119,10 @@ casttest!(wallet_sign_typed_data_string, |_prj, cmd| { "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", "{\"types\": {\"EIP712Domain\": [{\"name\": \"name\",\"type\": \"string\"},{\"name\": \"version\",\"type\": \"string\"},{\"name\": \"chainId\",\"type\": \"uint256\"},{\"name\": \"verifyingContract\",\"type\": \"address\"}],\"Message\": [{\"name\": \"data\",\"type\": \"string\"}]},\"primaryType\": \"Message\",\"domain\": {\"name\": \"example.metamask.io\",\"version\": \"1\",\"chainId\": \"1\",\"verifyingContract\": \"0x0000000000000000000000000000000000000000\"},\"message\": {\"data\": \"Hello!\"}}", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); + ]).assert_success().stdout_eq(str![[r#" +0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b + +"#]]); }); // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON file @@ -137,9 +140,10 @@ casttest!(wallet_sign_typed_data_file, |_prj, cmd| { .into_string() .unwrap() .as_str(), - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); + ]).assert_success().stdout_eq(str![[r#" +0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b + +"#]]); }); // tests that `cast wallet list` outputs the local accounts @@ -177,9 +181,12 @@ casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { "private-key", "test test test test test test test test test test test junk", "1", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +"#]]); }); // tests that `cast wallet private-key` with options outputs the private key diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index c58113039..a177d798e 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -42,7 +42,6 @@ alloy-signer-local = { workspace = true, features = [ parking_lot.workspace = true eyre.workspace = true -hex.workspace = true itertools.workspace = true jsonpath_lib.workspace = true revm.workspace = true @@ -58,3 +57,6 @@ semver.workspace = true rustc-hash.workspace = true dialoguer = "0.11.0" rand = "0.8" + +[dev-dependencies] +proptest.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index c977d7e3b..f9456dc8a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3551,6 +3551,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "deployCode_0", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "declaration": "function deployCode(string calldata artifactPath) external returns (address deployedAddress);", + "visibility": "external", + "mutability": "", + "signature": "deployCode(string)", + "selector": "0x9a8325a0", + "selectorBytes": [ + 154, + 131, + 37, + 160 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deployCode_1", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionaly accepts abi-encoded constructor arguments.", + "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress);", + "visibility": "external", + "mutability": "", + "signature": "deployCode(string,bytes)", + "selector": "0x29ce9dde", + "selectorBytes": [ + 41, + 206, + 157, + 222 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "deriveKey_0", @@ -4491,6 +4531,86 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "expectEmitAnonymous_0", + "description": "Prepare an expected anonymous log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an anonymous event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "declaration": "function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmitAnonymous(bool,bool,bool,bool,bool)", + "selector": "0xc948db5e", + "selectorBytes": [ + 201, + 72, + 219, + 94 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmitAnonymous_1", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmitAnonymous(bool,bool,bool,bool,bool,address)", + "selector": "0x71c95899", + "selectorBytes": [ + 113, + 201, + 88, + 153 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmitAnonymous_2", + "description": "Prepare an expected anonymous log with all topic and data checks enabled.\nCall this function, then emit an anonymous event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "declaration": "function expectEmitAnonymous() external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmitAnonymous()", + "selector": "0x2e5f270c", + "selectorBytes": [ + 46, + 95, + 39, + 12 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmitAnonymous_3", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmitAnonymous(address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmitAnonymous(address)", + "selector": "0x6fc68705", + "selectorBytes": [ + 111, + 198, + 135, + 5 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "expectEmit_0", @@ -5751,6 +5871,66 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "parseJsonTypeArray", + "description": "Parses a string of JSON data at `key` and coerces it to type array corresponding to `typeDescription`.", + "declaration": "function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonTypeArray(string,string,string)", + "selector": "0x0175d535", + "selectorBytes": [ + 1, + 117, + 213, + 53 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonType_0", + "description": "Parses a string of JSON data and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonType(string,string)", + "selector": "0xa9da313b", + "selectorBytes": [ + 169, + 218, + 49, + 59 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonType_1", + "description": "Parses a string of JSON data at `key` and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonType(string,string,string)", + "selector": "0xe3f5ae33", + "selectorBytes": [ + 227, + 245, + 174, + 51 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "parseJsonUint", @@ -6454,7 +6634,7 @@ { "func": { "id": "randomUint_1", - "description": "Returns random uin256 value between the provided range (min..=max).", + "description": "Returns random uin256 value between the provided range (=min..=max).", "declaration": "function randomUint(uint256 min, uint256 max) external returns (uint256);", "visibility": "external", "mutability": "", @@ -6971,26 +7151,6 @@ "status": "stable", "safety": "unsafe" }, - { - "func": { - "id": "rpc", - "description": "Performs an Ethereum JSON-RPC request to the current fork URL.", - "declaration": "function rpc(string calldata method, string calldata params) external returns (bytes memory data);", - "visibility": "external", - "mutability": "", - "signature": "rpc(string,string)", - "selector": "0x1206c8a8", - "selectorBytes": [ - 18, - 6, - 200, - 168 - ] - }, - "group": "evm", - "status": "stable", - "safety": "safe" - }, { "func": { "id": "rpcUrl", @@ -7051,6 +7211,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "rpc_0", + "description": "Performs an Ethereum JSON-RPC request to the current fork URL.", + "declaration": "function rpc(string calldata method, string calldata params) external returns (bytes memory data);", + "visibility": "external", + "mutability": "", + "signature": "rpc(string,string)", + "selector": "0x1206c8a8", + "selectorBytes": [ + 18, + 6, + 200, + 168 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rpc_1", + "description": "Performs an Ethereum JSON-RPC request to the given endpoint.", + "declaration": "function rpc(string calldata urlOrAlias, string calldata method, string calldata params) external returns (bytes memory data);", + "visibility": "external", + "mutability": "", + "signature": "rpc(string,string,string)", + "selector": "0x0199a220", + "selectorBytes": [ + 1, + 153, + 162, + 32 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "selectFork", @@ -7291,6 +7491,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "serializeJsonType_0", + "description": "See `serializeJson`.", + "declaration": "function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);", + "visibility": "external", + "mutability": "pure", + "signature": "serializeJsonType(string,bytes)", + "selector": "0x6d4f96a6", + "selectorBytes": [ + 109, + 79, + 150, + 166 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeJsonType_1", + "description": "See `serializeJson`.", + "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeJsonType(string,string,string,bytes)", + "selector": "0x6f93bccb", + "selectorBytes": [ + 111, + 147, + 188, + 203 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "serializeString_0", @@ -7391,6 +7631,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "setBlockhash", + "description": "Set blockhash for the current block.\nIt only sets the blockhash for blocks where `block.number - 256 <= number < block.number`.", + "declaration": "function setBlockhash(uint256 blockNumber, bytes32 blockHash) external;", + "visibility": "external", + "mutability": "", + "signature": "setBlockhash(uint256,bytes32)", + "selector": "0x5314b54a", + "selectorBytes": [ + 83, + 20, + 181, + 74 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "setEnv", diff --git a/crates/cheatcodes/common/src/record.rs b/crates/cheatcodes/common/src/record.rs index 9032870bd..decd8a17c 100644 --- a/crates/cheatcodes/common/src/record.rs +++ b/crates/cheatcodes/common/src/record.rs @@ -10,3 +10,18 @@ pub struct RecordAccess { /// Storage slots writes. pub writes: HashMap>, } + +impl RecordAccess { + /// Records a read access to a storage slot. + pub fn record_read(&mut self, target: Address, slot: U256) { + self.reads.entry(target).or_default().push(slot); + } + + /// Records a write access to a storage slot. + /// + /// This also records a read internally as `SSTORE` does an implicit `SLOAD`. + pub fn record_write(&mut self, target: Address, slot: U256) { + self.record_read(target, slot); + self.writes.entry(target).or_default().push(slot); + } +} diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 3ddd101a9..7fe6812a7 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -430,6 +430,11 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getBlobBaseFee() external view returns (uint256 blobBaseFee); + /// Set blockhash for the current block. + /// It only sets the blockhash for blocks where `block.number - 256 <= number < block.number`. + #[cheatcode(group = Evm, safety = Unsafe)] + function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; + // -------- Account State -------- /// Sets an address' balance. @@ -611,6 +616,12 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function rpc(string calldata method, string calldata params) external returns (bytes memory data); + /// Performs an Ethereum JSON-RPC request to the given endpoint. + #[cheatcode(group = Evm, safety = Safe)] + function rpc(string calldata urlOrAlias, string calldata method, string calldata params) + external + returns (bytes memory data); + /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) @@ -770,6 +781,27 @@ interface Vm { #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(address emitter) external; + /// Prepare an expected anonymous log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). + /// Call this function, then emit an anonymous 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). + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; + + /// Same as the previous method, but also checks supplied address against emitting contract. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) + external; + + /// Prepare an expected anonymous log with all topic and data checks enabled. + /// Call this function, then emit an anonymous 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. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmitAnonymous() external; + + /// Same as the previous method, but also checks supplied address against emitting contract. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmitAnonymous(address emitter) external; + /// Expects an error on next call with any revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert() external; @@ -1482,6 +1514,18 @@ interface Vm { #[cheatcode(group = Filesystem)] function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); + /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. + #[cheatcode(group = Filesystem)] + function deployCode(string calldata artifactPath) external returns (address deployedAddress); + + /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. + /// + /// Additionaly accepts abi-encoded constructor arguments. + #[cheatcode(group = Filesystem)] + function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress); + /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] @@ -1863,6 +1907,19 @@ interface Vm { pure returns (bytes32[] memory); + /// Parses a string of JSON data and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Json)] + function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of JSON data at `key` and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Json)] + function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of JSON data at `key` and coerces it to type array corresponding to `typeDescription`. + #[cheatcode(group = Json)] + function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) + external + pure + returns (bytes memory); + /// Returns an array of all the keys in a JSON object. #[cheatcode(group = Json)] function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys); @@ -1953,6 +2010,17 @@ interface Vm { function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) external returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeJsonType(string calldata typeDescription, bytes memory value) + external + pure + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) + external + returns (string memory json); // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-json to understand how // to use the JSON writing cheats. @@ -2156,7 +2224,7 @@ interface Vm { #[cheatcode(group = Utilities)] function randomUint() external returns (uint256); - /// Returns random uin256 value between the provided range (min..=max). + /// Returns random uin256 value between the provided range (=min..=max). #[cheatcode(group = Utilities)] function randomUint(uint256 min, uint256 max) external returns (uint256); diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 5761a1abe..039f49ccc 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -57,6 +57,8 @@ pub struct CheatsConfig { pub dual_compiled_contracts: DualCompiledContracts, /// Use ZK-VM on startup pub use_zk: bool, + /// Whether to enable legacy (non-reverting) assertions. + pub assertions_revert: bool, } impl CheatsConfig { @@ -99,6 +101,7 @@ impl CheatsConfig { running_version, dual_compiled_contracts, use_zk, + assertions_revert: config.assertions_revert, } } @@ -228,6 +231,7 @@ impl Default for CheatsConfig { running_version: Default::default(), dual_compiled_contracts: Default::default(), use_zk: false, + assertions_revert: true, } } } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index ec4459d3b..26aba7348 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -1,11 +1,11 @@ use crate::Vm; -use alloy_primitives::{Address, Bytes}; +use alloy_primitives::{hex, Address, Bytes}; use alloy_signer::Error as SignerError; use alloy_signer_local::LocalSignerError; use alloy_sol_types::SolError; use foundry_common::errors::FsPathError; use foundry_config::UnresolvedEnvVarError; -use foundry_evm_core::backend::DatabaseError; +use foundry_evm_core::backend::{BackendError, DatabaseError}; use foundry_wallets::error::WalletSignerError; use k256::ecdsa::signature::Error as SignatureError; use revm::primitives::EVMError; @@ -286,10 +286,12 @@ macro_rules! impl_from { impl_from!( alloy_sol_types::Error, + alloy_dyn_abi::Error, alloy_primitives::SignatureError, FsPathError, hex::FromHexError, eyre::Error, + BackendError, DatabaseError, jsonpath_lib::JsonPathError, serde_json::Error, @@ -304,10 +306,10 @@ impl_from!( WalletSignerError, ); -impl From> for Error { +impl> From> for Error { #[inline] - fn from(err: EVMError) -> Self { - Self::display(DatabaseError::from(err)) + fn from(err: EVMError) -> Self { + Self::display(BackendError::from(err)) } } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 1d530818d..9fcebcc3e 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -6,17 +6,15 @@ use alloy_primitives::{Address, Bytes, B256, U256}; 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, RevertSnapshotAction}, - constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, + constants::{CALLER, CHEATCODE_ADDRESS, TEST_CONTRACT_ADDRESS}, }; use revm::{ primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}, InnerEvmContext, }; -use std::{ - collections::{BTreeMap, HashMap}, - path::Path, -}; +use std::{collections::BTreeMap, path::Path}; mod fork; pub(crate) mod mapping; @@ -43,7 +41,7 @@ impl Cheatcode for addrCall { } impl Cheatcode for getNonce_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; if ccx.state.use_zk_vm { @@ -56,7 +54,7 @@ impl Cheatcode for getNonce_0Call { } impl Cheatcode for loadCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); ccx.ecx.load_account(target)?; @@ -66,7 +64,7 @@ impl Cheatcode for loadCall { } impl Cheatcode for loadAllocsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); @@ -92,7 +90,7 @@ impl Cheatcode for loadAllocsCall { } impl Cheatcode for dumpStateCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); @@ -130,7 +128,7 @@ impl Cheatcode for dumpStateCall { }, ) }) - .collect::>(); + .collect::>(); write_json_file(path, &alloc)?; Ok(Default::default()) @@ -138,28 +136,28 @@ impl Cheatcode for dumpStateCall { } impl Cheatcode for sign_0Call { - fn apply_full(&self, _: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, _: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; super::utils::sign(privateKey, digest) } } impl Cheatcode for sign_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { digest } = self; super::utils::sign_with_wallet(ccx, None, digest) } } impl Cheatcode for sign_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer, digest } = self; super::utils::sign_with_wallet(ccx, Some(*signer), digest) } } impl Cheatcode for signP256Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; super::utils::sign_p256(privateKey, digest, ccx.state) } @@ -237,7 +235,7 @@ impl Cheatcode for lastCallGasCall { } impl Cheatcode for chainIdCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); ccx.ecx.env.cfg.chain_id = newChainId.to(); @@ -246,7 +244,7 @@ impl Cheatcode for chainIdCall { } impl Cheatcode for coinbaseCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; ccx.ecx.env.block.coinbase = *newCoinbase; Ok(Default::default()) @@ -254,7 +252,7 @@ impl Cheatcode for coinbaseCall { } impl Cheatcode for difficultyCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( ccx.ecx.spec_id() < SpecId::MERGE, @@ -267,7 +265,7 @@ impl Cheatcode for difficultyCall { } impl Cheatcode for feeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; ccx.ecx.env.block.basefee = *newBasefee; Ok(Default::default()) @@ -275,7 +273,7 @@ impl Cheatcode for feeCall { } impl Cheatcode for prevrandao_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -288,7 +286,7 @@ impl Cheatcode for prevrandao_0Call { } impl Cheatcode for prevrandao_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -301,7 +299,7 @@ impl Cheatcode for prevrandao_1Call { } impl Cheatcode for blobhashesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { hashes } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -314,7 +312,7 @@ impl Cheatcode for blobhashesCall { } impl Cheatcode for getBlobhashesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -326,7 +324,7 @@ impl Cheatcode for getBlobhashesCall { } impl Cheatcode for rollCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; if ccx.state.use_zk_vm { foundry_zksync_core::cheatcodes::roll(*newHeight, ccx.ecx); @@ -339,14 +337,14 @@ impl Cheatcode for rollCall { } impl Cheatcode for getBlockNumberCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.number.abi_encode()) } } impl Cheatcode for txGasPriceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; ccx.ecx.env.tx.gas_price = *newGasPrice; Ok(Default::default()) @@ -354,7 +352,7 @@ impl Cheatcode for txGasPriceCall { } impl Cheatcode for warpCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; if ccx.state.use_zk_vm { foundry_zksync_core::cheatcodes::warp(*newTimestamp, ccx.ecx); @@ -367,14 +365,14 @@ impl Cheatcode for warpCall { } impl Cheatcode for getBlockTimestampCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.timestamp.abi_encode()) } } impl Cheatcode for blobBaseFeeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBlobBaseFee } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -387,14 +385,14 @@ impl Cheatcode for blobBaseFeeCall { } impl Cheatcode for getBlobBaseFeeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) } } impl Cheatcode for dealCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; let old_balance = if ccx.state.use_zk_vm { foundry_zksync_core::cheatcodes::deal(address, new_balance, ccx.ecx) @@ -402,7 +400,6 @@ impl Cheatcode for dealCall { let account = journaled_account(ccx.ecx, address)?; std::mem::replace(&mut account.info.balance, new_balance) }; - let record = DealRecord { address, old_balance, new_balance }; ccx.state.eth_deals.push(record); Ok(Default::default()) @@ -410,7 +407,7 @@ impl Cheatcode for dealCall { } impl Cheatcode for etchCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; if ccx.state.use_zk_vm { foundry_zksync_core::cheatcodes::etch(*target, newRuntimeBytecode, ccx.ecx); @@ -427,7 +424,7 @@ impl Cheatcode for etchCall { } impl Cheatcode for resetNonceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; if ccx.state.use_zk_vm { foundry_zksync_core::cheatcodes::set_nonce(*account, U256::ZERO, ccx.ecx); @@ -447,7 +444,7 @@ impl Cheatcode for resetNonceCall { } impl Cheatcode for setNonceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; if ccx.state.use_zk_vm { @@ -469,7 +466,7 @@ impl Cheatcode for setNonceCall { } impl Cheatcode for setNonceUnsafeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; if ccx.state.use_zk_vm { @@ -484,7 +481,7 @@ impl Cheatcode for setNonceUnsafeCall { } impl Cheatcode for storeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot, value } = *self; ensure_not_precompile!(&target, ccx); // ensure the account is touched @@ -495,7 +492,7 @@ impl Cheatcode for storeCall { } impl Cheatcode for coolCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); @@ -506,21 +503,21 @@ impl Cheatcode for coolCall { } impl Cheatcode for readCallersCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; read_callers(ccx.state, &ccx.ecx.env.tx.caller) } } impl Cheatcode for snapshotCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.db.snapshot(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } impl Cheatcode for revertToCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, @@ -539,7 +536,7 @@ impl Cheatcode for revertToCall { } impl Cheatcode for revertToAndDeleteCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, @@ -558,14 +555,14 @@ impl Cheatcode for revertToAndDeleteCall { } impl Cheatcode for deleteSnapshotCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = ccx.ecx.db.delete_snapshot(*snapshotId); Ok(result.abi_encode()) } } impl Cheatcode for deleteSnapshotsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx.db.delete_snapshots(); Ok(Default::default()) @@ -587,6 +584,20 @@ impl Cheatcode for stopAndReturnStateDiffCall { } } +impl Cheatcode for setBlockhashCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { blockNumber, blockHash } = *self; + ensure!( + blockNumber <= ccx.ecx.env.block.number, + "block number must be less than or equal to the current block number" + ); + + ccx.ecx.db.set_blockhash(blockNumber, blockHash); + + Ok(Default::default()) + } +} + pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { let (account, _) = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?; Ok(account.info.nonce.abi_encode()) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 8875369c1..f48ed63c7 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,4 +1,5 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use alloy_dyn_abi::DynSolValue; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::Filter; @@ -7,7 +8,7 @@ use foundry_common::provider::ProviderBuilder; use foundry_evm_core::fork::CreateFork; impl Cheatcode for activeForkCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx .db @@ -18,49 +19,49 @@ impl Cheatcode for activeForkCall { } impl Cheatcode for createFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -74,7 +75,7 @@ impl Cheatcode for rollFork_0Call { } impl Cheatcode for rollFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -88,7 +89,7 @@ impl Cheatcode for rollFork_1Call { } impl Cheatcode for rollFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -102,7 +103,7 @@ impl Cheatcode for rollFork_2Call { } impl Cheatcode for rollFork_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -116,7 +117,7 @@ impl Cheatcode for rollFork_3Call { } impl Cheatcode for selectForkCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; @@ -129,35 +130,43 @@ impl Cheatcode for selectForkCall { } impl Cheatcode for transact_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { let Self { txHash } = *self; ccx.ecx.db.transact( None, txHash, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state, - ccx.state, + &mut executor.get_inspector(ccx.state), )?; Ok(Default::default()) } } impl Cheatcode for transact_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { let Self { forkId, txHash } = *self; ccx.ecx.db.transact( Some(forkId), txHash, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state, - ccx.state, + &mut executor.get_inspector(ccx.state), )?; Ok(Default::default()) } } impl Cheatcode for allowCheatcodesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.allow_cheatcode_access(*account); Ok(Default::default()) @@ -165,7 +174,7 @@ impl Cheatcode for allowCheatcodesCall { } impl Cheatcode for makePersistent_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.add_persistent_account(*account); Ok(Default::default()) @@ -173,7 +182,7 @@ impl Cheatcode for makePersistent_0Call { } impl Cheatcode for makePersistent_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -182,7 +191,7 @@ impl Cheatcode for makePersistent_1Call { } impl Cheatcode for makePersistent_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -192,15 +201,17 @@ impl Cheatcode for makePersistent_2Call { } impl Cheatcode for makePersistent_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.ecx.db.extend_persistent_accounts(accounts.iter().copied()); + for account in accounts { + ccx.ecx.db.add_persistent_account(*account); + } Ok(Default::default()) } } impl Cheatcode for revokePersistent_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.remove_persistent_account(account); Ok(Default::default()) @@ -208,40 +219,41 @@ impl Cheatcode for revokePersistent_0Call { } impl Cheatcode for revokePersistent_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.ecx.db.remove_persistent_accounts(accounts.iter().copied()); + for account in accounts { + ccx.ecx.db.remove_persistent_account(account); + } Ok(Default::default()) } } impl Cheatcode for isPersistentCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; Ok(ccx.ecx.db.is_persistent(account).abi_encode()) } } -impl Cheatcode for rpcCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for rpc_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; - let provider = ProviderBuilder::new(&url).build()?; - let params_json: serde_json::Value = serde_json::from_str(params)?; - let result = - foundry_common::block_on(provider.raw_request(method.clone().into(), params_json)) - .map_err(|err| fmt_err!("{method:?}: {err}"))?; - - let result_as_tokens = crate::json::json_value_to_token(&result) - .map_err(|err| fmt_err!("failed to parse result: {err}"))?; + rpc_call(&url, method, params) + } +} - Ok(result_as_tokens.abi_encode()) +impl Cheatcode for rpc_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { urlOrAlias, method, params } = self; + let url = state.config.rpc_url(urlOrAlias)?; + rpc_call(&url, method, params) } } impl Cheatcode for eth_getLogsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { @@ -375,3 +387,26 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { fn persist_caller(ccx: &mut CheatsCtxt) { ccx.ecx.db.add_persistent_account(ccx.caller); } + +/// Performs an Ethereum JSON-RPC request to the given endpoint. +fn rpc_call(url: &str, method: &str, params: &str) -> Result { + let provider = ProviderBuilder::new(url).build()?; + let params_json: serde_json::Value = serde_json::from_str(params)?; + let result = + foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) + .map_err(|err| fmt_err!("{method:?}: {err}"))?; + + let result_as_tokens = match crate::json::json_value_to_token(&result) + .map_err(|err| fmt_err!("failed to parse result: {err}"))? + { + // Convert fixed bytes to bytes to prevent encoding issues. + // See: + DynSolValue::FixedBytes(bytes, size) => { + DynSolValue::Bytes(bytes.as_slice()[..size].to_vec()) + } + DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), + val => val, + }; + + Ok(result_as_tokens.abi_encode()) +} diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs index b506d2058..679609274 100644 --- a/crates/cheatcodes/src/evm/mapping.rs +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -112,7 +112,7 @@ fn slot_child<'a>( mapping_slot(state, target)?.children.get(slot) } -#[inline] +#[cold] pub(crate) fn step(mapping_slots: &mut HashMap, interpreter: &Interpreter) { match interpreter.current_opcode() { opcode::KECCAK256 => { diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 48e3cff0d..fc3742d82 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -12,19 +12,16 @@ impl Cheatcode for clearMockedCallsCall { } impl Cheatcode for mockCall_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - // TODO: use ecx.load_account - let (acc, _) = ccx.ecx.journaled_state.load_account(*callee, &mut ccx.ecx.db)?; + let (acc, _) = ccx.ecx.load_account(*callee)?; // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); if empty_bytecode { - let code = revm::interpreter::analysis::to_analysed(Bytecode::new_raw( - Bytes::copy_from_slice(&foundry_zksync_core::EMPTY_CODE), - )); - ccx.ecx.journaled_state.set_code(*callee, code.clone()); + let code = Bytecode::new_raw(Bytes::copy_from_slice(&foundry_zksync_core::EMPTY_CODE)); + ccx.ecx.journaled_state.set_code(*callee, code); } if ccx.state.use_zk_vm { @@ -37,7 +34,7 @@ impl Cheatcode for mockCall_0Call { } impl Cheatcode for mockCall_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; ccx.ecx.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 4e4ef81f7..fe5418b31 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -45,28 +45,28 @@ impl Prank { } impl Cheatcode for prank_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true) } } impl Cheatcode for startPrank_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false) } } impl Cheatcode for prank_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true) } } impl Cheatcode for startPrank_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false) } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index e4ee12513..129f8a22e 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,14 +1,16 @@ //! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. use super::string::parse; -use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_primitives::{Bytes, U256}; +use alloy_primitives::{hex, Bytes, U256}; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::backend::DatabaseExt; +use revm::interpreter::CreateInputs; use semver::Version; use std::{ collections::hash_map::Entry, @@ -262,6 +264,57 @@ impl Cheatcode for getDeployedCodeCall { } } +impl Cheatcode for deployCode_0Call { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { artifactPath: path } = self; + let bytecode = get_artifact_code(ccx.state, path, false)?; + let output = executor + .exec_create( + CreateInputs { + caller: ccx.caller, + scheme: revm::primitives::CreateScheme::Create, + value: U256::ZERO, + init_code: bytecode, + gas_limit: ccx.gas_limit, + }, + ccx, + ) + .unwrap(); + + Ok(output.address.unwrap().abi_encode()) + } +} + +impl Cheatcode for deployCode_1Call { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { artifactPath: path, constructorArgs } = self; + let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec(); + bytecode.extend_from_slice(constructorArgs); + let output = executor + .exec_create( + CreateInputs { + caller: ccx.caller, + scheme: revm::primitives::CreateScheme::Create, + value: U256::ZERO, + init_code: bytecode.into(), + gas_limit: ccx.gas_limit, + }, + ccx, + ) + .unwrap(); + + Ok(output.address.unwrap().abi_encode()) + } +} + /// Returns the path to the json artifact depending on the input /// /// Can parse following input formats: diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 85c7e7310..6057d0b1a 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1,4 +1,4 @@ -//! Cheatcode EVM [Inspector]. +//! Cheatcode EVM inspector. use crate::{ evm::{ @@ -7,14 +7,15 @@ use crate::{ prank::Prank, DealRecord, }, + inspector::utils::CommonCreateInput, script::{Broadcast, ScriptWallets}, test::expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, - CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, - Vm::{self, AccountAccess}, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm, + Vm::AccountAccess, }; -use alloy_primitives::{keccak256, Address, Bytes, Log, TxKind, B256, U256}; +use alloy_primitives::{hex, keccak256, Address, Bytes, Log, TxKind, B256, U256}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; -use alloy_sol_types::{SolInterface, SolValue}; +use alloy_sol_types::{SolCall, SolInterface, SolValue}; use foundry_cheatcodes_common::{ expect::{ExpectedCallData, ExpectedCallTracker, ExpectedCallType}, mock::{MockCallDataContext, MockCallReturnData}, @@ -23,12 +24,10 @@ use foundry_cheatcodes_common::{ use foundry_common::{evm::Breakpoints, SELECTOR_LEN}; use foundry_config::Config; use foundry_evm_core::{ - abi::Vm::stopExpectSafeMemoryCall, + abi::{Vm::stopExpectSafeMemoryCall, HARDHAT_CONSOLE_ADDRESS}, backend::{DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic}, - constants::{ - CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER_CODE, - HARDHAT_CONSOLE_ADDRESS, - }, + constants::{CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER_CODE}, + utils::new_evm_with_existing_context, InspectorExt, }; use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; @@ -39,12 +38,12 @@ use foundry_zksync_core::{ use itertools::Itertools; use revm::{ interpreter::{ - opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, - InstructionResult, Interpreter, InterpreterAction, InterpreterResult, + opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, + Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, primitives::{ - AccountInfo, BlockEnv, Bytecode, CreateScheme, Env, EvmStorageSlot, ExecutionResult, - HashMap as rHashMap, Output, TransactTo, KECCAK_EMPTY, + AccountInfo, BlockEnv, Bytecode, CreateScheme, EVMError, Env, EvmStorageSlot, + ExecutionResult, HashMap as rHashMap, Output, TransactTo, KECCAK_EMPTY, }, EvmContext, InnerEvmContext, Inspector, }; @@ -66,7 +65,115 @@ use zksync_types::{ SYSTEM_CONTEXT_ADDRESS, }; -macro_rules! try_or_continue { +mod utils; + +/// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to +/// [Cheatcodes]. +/// +/// This is needed for cases when inspector itself needs mutable access to [Cheatcodes] state and +/// allows us to correctly execute arbitrary EVM frames from inside cheatcode implementations. +pub trait CheatcodesExecutor { + /// Core trait method accepting mutable reference to [Cheatcodes] and returning + /// [revm::Inspector]. + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a; + + /// Constructs [revm::Evm] and runs a given closure with it. + fn with_evm( + &mut self, + ccx: &mut CheatsCtxt, + f: F, + ) -> Result> + where + F: for<'a, 'b> FnOnce( + &mut revm::Evm< + '_, + &'b mut dyn InspectorExt<&'a mut dyn DatabaseExt>, + &'a mut dyn DatabaseExt, + >, + ) -> Result>, + { + let mut inspector = self.get_inspector(ccx.state); + let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); + let l1_block_info = std::mem::take(&mut ccx.ecx.l1_block_info); + + let inner = revm::InnerEvmContext { + env: ccx.ecx.env.clone(), + journaled_state: std::mem::replace( + &mut ccx.ecx.journaled_state, + revm::JournaledState::new(Default::default(), Default::default()), + ), + db: &mut ccx.ecx.db as &mut dyn DatabaseExt, + error, + l1_block_info, + valid_authorizations: std::mem::take(&mut ccx.ecx.valid_authorizations), + }; + + let mut evm = new_evm_with_existing_context(inner, &mut inspector as _); + + let res = f(&mut evm)?; + + ccx.ecx.journaled_state = evm.context.evm.inner.journaled_state; + ccx.ecx.env = evm.context.evm.inner.env; + ccx.ecx.l1_block_info = evm.context.evm.inner.l1_block_info; + ccx.ecx.error = evm.context.evm.inner.error; + ccx.ecx.valid_authorizations = evm.context.evm.inner.valid_authorizations; + + Ok(res) + } + + /// Obtains [revm::Evm] instance and executes the given CREATE frame. + fn exec_create( + &mut self, + inputs: CreateInputs, + ccx: &mut CheatsCtxt, + ) -> Result> { + self.with_evm(ccx, |evm| { + evm.context.evm.inner.journaled_state.depth += 1; + + let first_frame_or_result = + evm.handler.execution().create(&mut evm.context, Box::new(inputs))?; + + let mut result = match first_frame_or_result { + revm::FrameOrResult::Frame(first_frame) => evm.run_the_loop(first_frame)?, + revm::FrameOrResult::Result(result) => result, + }; + + evm.handler.execution().last_frame_return(&mut evm.context, &mut result)?; + + let outcome = match result { + revm::FrameResult::Call(_) | revm::FrameResult::EOFCreate(_) => unreachable!(), + revm::FrameResult::Create(create) => create, + }; + + evm.context.evm.inner.journaled_state.depth -= 1; + + Ok(outcome) + }) + } + + fn console_log(&mut self, ccx: &mut CheatsCtxt, message: String) { + self.get_inspector::(ccx.state).console_log(message); + } +} + +/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an +/// inspector. +#[derive(Debug, Default, Clone, Copy)] +struct TransparentCheatcodesExecutor; + +impl CheatcodesExecutor for TransparentCheatcodesExecutor { + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a { + cheats + } +} + +macro_rules! try_or_return { ($e:expr) => { match $e { Ok(v) => v, @@ -278,7 +385,7 @@ impl Cheatcodes { zk_factory_deps: Default::default(), evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), - evm_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), }); let cheatcodes_bytecode = { @@ -342,10 +449,12 @@ impl Cheatcodes { self.config.script_wallets.as_ref() } - fn apply_cheatcode( + /// Decodes the input data and applies the cheatcode. + fn apply_cheatcode( &mut self, ecx: &mut EvmContext, call: &CallInputs, + executor: &mut E, ) -> Result { // decode the cheatcode call let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { @@ -371,37 +480,27 @@ impl Cheatcodes { state: self, ecx: &mut ecx.inner, precompiles: &mut ecx.precompiles, + gas_limit: call.gas_limit, caller, }, + executor, ) } - /// Determines the address of the contract and marks it as allowed - /// Returns the address of the contract created + /// Grants cheat code access for new contracts if the caller also has + /// cheatcode access or the new contract is created in top most call. /// /// There may be cheatcodes in the constructor of the new contract, in order to allow them - /// automatically we need to determine the new address + /// automatically we need to determine the new address. fn allow_cheatcodes_on_create( &self, ecx: &mut InnerEvmContext, - inputs: &CreateInputs, - ) -> Address { - let old_nonce = ecx - .journaled_state - .state - .get(&inputs.caller) - .map(|acc| acc.info.nonce) - .unwrap_or_default(); - let created_address = inputs.created_address(old_nonce); - if ecx.journaled_state.depth > 1 && !ecx.db.has_cheatcode_access(&inputs.caller) { - // we only grant cheat code access for new contracts if the caller also has - // cheatcode access and the new contract is created in top most call - return created_address; + caller: Address, + created_address: Address, + ) { + if ecx.journaled_state.depth <= 1 || ecx.db.has_cheatcode_access(&caller) { + ecx.db.allow_cheatcode_access(created_address); } - - ecx.db.allow_cheatcode_access(created_address); - - created_address } /// Called when there was a revert. @@ -430,7 +529,6 @@ impl Cheatcodes { } } } - /// Selects the appropriate VM for the fork. Options: EVM, ZK-VM. /// CALL and CREATE are handled by the selected VM. /// @@ -630,507 +728,380 @@ impl Cheatcodes { } } } -} - -impl Inspector for Cheatcodes { - #[inline] - fn initialize_interp(&mut self, _: &mut Interpreter, ecx: &mut EvmContext) { - // When the first interpreter is initialized we've circumvented the balance and gas checks, - // so we apply our actual block data with the correct fees and all. - if let Some(block) = self.block.take() { - ecx.env.block = block; - } - if let Some(gas_price) = self.gas_price.take() { - ecx.env.tx.gas_price = gas_price; - } - if self.startup_zk && !self.use_zk_vm { - self.startup_zk = false; // We only do this once. - self.select_zk_vm(ecx, None); - } - } - #[inline] - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { - // ovverride address(x).balance retrieval to make it consistent between EraVM and EVM - if self.use_zk_vm { - let address = match interpreter.current_opcode() { - opcode::SELFBALANCE => interpreter.contract().target_address, - opcode::BALANCE => { - if interpreter.stack.is_empty() { - interpreter.instruction_result = InstructionResult::StackUnderflow; - return; - } + // common create functionality for both legacy and EOF. + fn create_common( + &mut self, + ecx: &mut EvmContext, + mut input: Input, + ) -> Option + where + DB: DatabaseExt, + Input: CommonCreateInput, + { + let ecx_inner = &mut ecx.inner; + let gas = Gas::new(input.gas_limit()); - Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) + // Apply our prank + if let Some(prank) = &self.prank { + if ecx_inner.journaled_state.depth() >= prank.depth && + input.caller() == prank.prank_caller + { + // At the target depth we set `msg.sender` + if ecx_inner.journaled_state.depth() == prank.depth { + input.set_caller(prank.new_caller); } - _ => return, - }; - - // Safety: Length is checked above. - let balance = foundry_zksync_core::balance(address, ecx); - // Skip the current BALANCE instruction since we've already handled it - match interpreter.stack.push(balance) { - Ok(_) => unsafe { - interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); - }, - Err(e) => { - interpreter.instruction_result = e; + // At the target depth, or deeper, we set `tx.origin` + if let Some(new_origin) = prank.new_origin { + ecx_inner.env.tx.caller = new_origin; } } } - } - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { - let ecx = &mut ecx.inner; - self.pc = interpreter.program_counter(); + // Apply our broadcast + if let Some(broadcast) = &self.broadcast { + if ecx_inner.journaled_state.depth() >= broadcast.depth && + input.caller() == broadcast.original_caller + { + if let Err(err) = + ecx_inner.journaled_state.load_account(broadcast.new_origin, &mut ecx_inner.db) + { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }); + } - // reset gas if gas metering is turned off - match self.gas_metering { - Some(None) => { - // need to store gas metering - self.gas_metering = Some(Some(interpreter.gas)); - } - Some(Some(gas)) => { - match interpreter.current_opcode() { - opcode::CREATE | opcode::CREATE2 => { - // set we're about to enter CREATE frame to meter its gas on first opcode - // inside it - self.gas_metering_create = Some(None) - } - opcode::STOP | opcode::RETURN | opcode::SELFDESTRUCT | opcode::REVERT => { - // If we are ending current execution frame, we want to just fully reset gas - // otherwise weird things with returning gas from a call happen - // ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/evm_impl.rs#L190 - // - // It would be nice if we had access to the interpreter in `call_end`, as we - // could just do this there instead. - match self.gas_metering_create { - None | Some(None) => { - interpreter.gas = Gas::new(0); - } - Some(Some(gas)) => { - // If this was CREATE frame, set correct gas limit. This is needed - // because CREATE opcodes deduct additional gas for code storage, - // and deducted amount is compared to gas limit. If we set this to - // 0, the CREATE would fail with out of gas. - // - // If we however set gas limit to the limit of outer frame, it would - // cause a panic after erasing gas cost post-create. Reason for this - // is pre-create REVM records `gas_limit - (gas_limit / 64)` as gas - // used, and erases costs by `remaining` gas post-create. - // gas used ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/instructions/host.rs#L254-L258 - // post-create erase ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/instructions/host.rs#L279 - interpreter.gas = Gas::new(gas.limit()); + ecx_inner.env.tx.caller = broadcast.new_origin; - // reset CREATE gas metering because we're about to exit its frame - self.gas_metering_create = None - } - } - } - _ => { - // if just starting with CREATE opcodes, record its inner frame gas - if let Some(None) = self.gas_metering_create { - self.gas_metering_create = Some(Some(interpreter.gas)) - } + if ecx_inner.journaled_state.depth() == broadcast.depth { + input.set_caller(broadcast.new_origin); + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, input.gas_limit()); - // dont monitor gas changes, keep it constant - interpreter.gas = gas; - } - } - } - _ => {} - } + let mut to = None; + let mut nonce: u64 = + ecx_inner.journaled_state.state()[&broadcast.new_origin].info.nonce; + //drop the mutable borrow of account + let mut call_init_code = input.init_code(); + let mut zk_tx = if self.use_zk_vm { + to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); + nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; + let contract = self + .dual_compiled_contracts + .find_by_evm_bytecode(&input.init_code().0) + .unwrap_or_else(|| { + panic!("failed finding contract for {:?}", input.init_code()) + }); + let factory_deps = + self.dual_compiled_contracts.fetch_all_factory_deps(contract); - // Record writes and reads if `record` has been called - if let Some(storage_accesses) = &mut self.accesses { - match interpreter.current_opcode() { - opcode::SLOAD => { - let key = try_or_continue!(interpreter.stack().peek(0)); - storage_accesses - .reads - .entry(interpreter.contract().target_address) - .or_default() - .push(key); - } - opcode::SSTORE => { - let key = try_or_continue!(interpreter.stack().peek(0)); - - // An SSTORE does an SLOAD internally - storage_accesses - .reads - .entry(interpreter.contract().target_address) - .or_default() - .push(key); - storage_accesses - .writes - .entry(interpreter.contract().target_address) - .or_default() - .push(key); - } - _ => (), - } - } + let constructor_input = + call_init_code[contract.evm_bytecode.len()..].to_vec(); - // Record account access via SELFDESTRUCT if `recordAccountAccesses` has been called - if let Some(account_accesses) = &mut self.recorded_account_diffs_stack { - if interpreter.current_opcode() == opcode::SELFDESTRUCT { - let target = try_or_continue!(interpreter.stack().peek(0)); - // load balance of this account - let value = ecx - .balance(interpreter.contract().target_address) - .map(|(b, _)| b) - .unwrap_or(U256::ZERO); - let account = Address::from_word(B256::from(target)); - // get previous balance and initialized status of the target account - // TODO: use load_account_exists - let (initialized, old_balance) = if let Ok((account, _)) = - ecx.journaled_state.load_account(account, &mut ecx.db) - { - (account.info.exists(), account.info.balance) - } else { - (false, U256::ZERO) - }; - // register access for the target account - let access = crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), - }, - accessor: interpreter.contract().target_address, - account, - kind: crate::Vm::AccountAccessKind::SelfDestruct, - initialized, - oldBalance: old_balance, - newBalance: old_balance + value, - value, - data: Bytes::new(), - reverted: false, - deployedCode: Bytes::new(), - storageAccesses: vec![], - depth: ecx.journaled_state.depth(), - }; - // Ensure that we're not selfdestructing a context recording was initiated on - if let Some(last) = account_accesses.last_mut() { - last.push(access); - } - } - } + let create_input = foundry_zksync_core::encode_create_params( + &input.scheme().unwrap_or(CreateScheme::Create), + contract.zk_bytecode_hash, + constructor_input, + ); + call_init_code = Bytes::from(create_input); - // Record granular ordered storage accesses if `startStateDiffRecording` has been called - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - match interpreter.current_opcode() { - opcode::SLOAD => { - let key = try_or_continue!(interpreter.stack().peek(0)); - let address = interpreter.contract().target_address; - - // Try to include present value for informational purposes, otherwise assume - // it's not set (zero value) - let mut present_value = U256::ZERO; - // Try to load the account and the slot's present value - if ecx.load_account(address).is_ok() { - if let Ok((previous, _)) = ecx.sload(address, key) { - present_value = previous; - } - } - let access = crate::Vm::StorageAccess { - account: interpreter.contract().target_address, - slot: key.into(), - isWrite: false, - previousValue: present_value.into(), - newValue: present_value.into(), - reverted: false, + Some(factory_deps) + } else { + None }; - append_storage_access( - recorded_account_diffs_stack, - access, - ecx.journaled_state.depth(), - ); - } - opcode::SSTORE => { - let key = try_or_continue!(interpreter.stack().peek(0)); - let value = try_or_continue!(interpreter.stack().peek(1)); - let address = interpreter.contract().target_address; - // Try to load the account and the slot's previous value, otherwise, assume it's - // not set (zero value) - let mut previous_value = U256::ZERO; - if ecx.load_account(address).is_ok() { - if let Ok((previous, _)) = ecx.sload(address, key) { - previous_value = previous; - } - } + let rpc = ecx_inner.db.active_fork_url(); + if let Some(factory_deps) = zk_tx { + let mut batched = + foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); + debug!(batches = batched.len(), "splitting factory deps for broadcast"); + // the last batch is the final one that does the deployment + zk_tx = batched.pop(); - let access = crate::Vm::StorageAccess { - account: address, - slot: key.into(), - isWrite: true, - previousValue: previous_value.into(), - newValue: value.into(), - reverted: false, - }; - append_storage_access( - recorded_account_diffs_stack, - access, - ecx.journaled_state.depth(), - ); - } - // Record account accesses via the EXT family of opcodes - opcode::EXTCODECOPY | - opcode::EXTCODESIZE | - opcode::EXTCODEHASH | - opcode::BALANCE => { - let kind = match interpreter.current_opcode() { - opcode::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, - opcode::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, - opcode::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, - opcode::BALANCE => crate::Vm::AccountAccessKind::Balance, - _ => unreachable!(), - }; - let address = Address::from_word(B256::from(try_or_continue!(interpreter - .stack() - .peek(0)))); - let balance; - let initialized; - // TODO: use ecx.load_account - if let Ok((acc, _)) = ecx.journaled_state.load_account(address, &mut ecx.db) { - initialized = acc.info.exists(); - balance = acc.info.balance; - } else { - initialized = false; - balance = U256::ZERO; + for factory_deps in batched { + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: rpc.clone(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::Call(Address::ZERO)), + value: Some(input.value()), + nonce: Some(nonce), + ..Default::default() + }, + zk_tx: Some(ZkTransactionMetadata { factory_deps }), + }); + + //update nonce for each tx + nonce += 1; + } } - let account_access = crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc, + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to, + value: Some(input.value()), + input: TransactionInput::new(call_init_code), + nonce: Some(nonce), + gas: if is_fixed_gas_limit { + Some(input.gas_limit() as u128) + } else { + None + }, + ..Default::default() }, - accessor: interpreter.contract().target_address, - account: address, - kind, - initialized, - oldBalance: balance, - newBalance: balance, - value: U256::ZERO, - data: Bytes::new(), - reverted: false, - deployedCode: Bytes::new(), - storageAccesses: vec![], - depth: ecx.journaled_state.depth(), - }; - // Record the EXT* call as an account access at the current depth - // (future storage accesses will be recorded in a new "Resume" context) - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.push(account_access); - } else { - recorded_account_diffs_stack.push(vec![account_access]); - } + zk_tx: zk_tx.map(ZkTransactionMetadata::new), + }); + + input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); } - _ => (), } } - // If the allowed memory writes cheatcode is active at this context depth, check to see - // if the current opcode can either mutate directly or expand memory. If the opcode at - // the current program counter is a match, check if the modified memory lies within the - // allowed ranges. If not, revert and fail the test. - if let Some(ranges) = self.allowed_mem_writes.get(&ecx.journaled_state.depth()) { - // The `mem_opcode_match` macro is used to match the current opcode against a list of - // opcodes that can mutate memory (either directly or expansion via reading). If the - // opcode is a match, the memory offsets that are being written to are checked to be - // within the allowed ranges. If not, the test is failed and the transaction is - // reverted. For all opcodes that can mutate memory aside from MSTORE, - // MSTORE8, and MLOAD, the size and destination offset are on the stack, and - // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the - // size of the memory write is implicit, so these cases are hard-coded. - macro_rules! mem_opcode_match { - ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => { - match interpreter.current_opcode() { - //////////////////////////////////////////////////////////////// - // OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING // - //////////////////////////////////////////////////////////////// - - opcode::MSTORE => { - // The offset of the mstore operation is at the top of the stack. - let offset = try_or_continue!(interpreter.stack().peek(0)).saturating_to::(); - - // If none of the allowed ranges contain [offset, offset + 32), memory has been - // unexpectedly mutated. - if !ranges.iter().any(|range| { - range.contains(&offset) && range.contains(&(offset + 31)) - }) { - // SPECIAL CASE: When the compiler attempts to store the selector for - // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory - // pointer, which could have been updated to the exclusive upper bound during - // execution. - let value = try_or_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>(); - let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; - if value[0..SELECTOR_LEN] == selector { - return - } + // Allow cheatcodes from the address of the new contract + let address = input.allow_cheatcodes(self, ecx); - disallowed_mem_write(offset, 32, interpreter, ranges); - return - } - } - opcode::MSTORE8 => { - // The offset of the mstore8 operation is at the top of the stack. - let offset = try_or_continue!(interpreter.stack().peek(0)).saturating_to::(); - - // If none of the allowed ranges contain the offset, memory has been - // unexpectedly mutated. - if !ranges.iter().any(|range| range.contains(&offset)) { - disallowed_mem_write(offset, 1, interpreter, ranges); - return - } - } + // If `recordAccountAccesses` has been called, record the create + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + recorded_account_diffs_stack.push(vec![AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), + }, + accessor: input.caller(), + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on (eof)create_end + newBalance: U256::ZERO, // updated on (eof)create_end + value: input.value(), + data: input.init_code(), + reverted: false, + deployedCode: Bytes::new(), // updated on (eof)create_end + storageAccesses: vec![], // updated on (eof)create_end + depth: ecx.journaled_state.depth(), + }]); + } - //////////////////////////////////////////////////////////////// - // OPERATIONS THAT CAN EXPAND MEMORY BY READING // - //////////////////////////////////////////////////////////////// - - opcode::MLOAD => { - // The offset of the mload operation is at the top of the stack - let offset = try_or_continue!(interpreter.stack().peek(0)).saturating_to::(); - - // If the offset being loaded is >= than the memory size, the - // memory is being expanded. If none of the allowed ranges contain - // [offset, offset + 32), memory has been unexpectedly mutated. - if offset >= interpreter.shared_memory.len() as u64 && !ranges.iter().any(|range| { - range.contains(&offset) && range.contains(&(offset + 31)) - }) { - disallowed_mem_write(offset, 32, interpreter, ranges); - return - } - } + if self.use_zk_vm { + info!("running create in zk vm"); + if input.init_code().0 == DEFAULT_CREATE2_DEPLOYER_CODE { + info!("ignoring DEFAULT_CREATE2_DEPLOYER_CODE for zk"); + return None + } - //////////////////////////////////////////////////////////////// - // OPERATIONS WITH OFFSET AND SIZE ON STACK // - //////////////////////////////////////////////////////////////// + let zk_contract = self + .dual_compiled_contracts + .find_by_evm_bytecode(&input.init_code().0) + .unwrap_or_else(|| panic!("failed finding contract for {:?}", input.init_code())); - opcode::CALL => { - // The destination offset of the operation is the fifth element on the stack. - let dest_offset = try_or_continue!(interpreter.stack().peek(5)).saturating_to::(); + let factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(zk_contract); + tracing::debug!(contract = zk_contract.name, "using dual compiled contract"); - // The size of the data that will be copied is the sixth element on the stack. - let size = try_or_continue!(interpreter.stack().peek(6)).saturating_to::(); + let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { + mocked_calls: self.mocked_calls.clone(), + expected_calls: Some(&mut self.expected_calls), + accesses: self.accesses.as_mut(), + persisted_factory_deps: Some(&mut self.persisted_factory_deps), + }; + let create_inputs = CreateInputs { + scheme: input.scheme().unwrap_or(CreateScheme::Create), + init_code: input.init_code(), + value: input.value(), + caller: input.caller(), + gas_limit: input.gas_limit(), + }; + if let Ok(result) = foundry_zksync_core::vm::create::<_, DatabaseError>( + &create_inputs, + zk_contract, + factory_deps, + ecx, + ccx, + ) { + self.combined_logs.extend(result.logs.clone().into_iter().map(Some)); - // If none of the allowed ranges contain [dest_offset, dest_offset + size), - // memory outside of the expected ranges has been touched. If the opcode - // only reads from memory, this is okay as long as the memory is not expanded. - let fail_cond = !ranges.iter().any(|range| { - range.contains(&dest_offset) && - range.contains(&(dest_offset + size.saturating_sub(1))) - }); + // for each log in cloned logs call handle_expect_emit + if !self.expected_emits.is_empty() { + for log in result.logs { + expect::handle_expect_emit(self, &log); + } + } - // If the failure condition is met, set the output buffer to a revert string - // that gives information about the allowed ranges and revert. - if fail_cond { - // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed. - // It allocated calldata at the current free memory pointer, and will attempt to read - // from this memory region to perform the call. - let to = Address::from_word(try_or_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>().into()); - if to == CHEATCODE_ADDRESS { - let args_offset = try_or_continue!(interpreter.stack().peek(3)).saturating_to::(); - let args_size = try_or_continue!(interpreter.stack().peek(4)).saturating_to::(); - let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; - let memory_word = interpreter.shared_memory.slice(args_offset, args_size); - if memory_word[0..SELECTOR_LEN] == selector { - return - } - } + return match result.execution_result { + ExecutionResult::Success { output, .. } => match output { + Output::Create(bytes, address) => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: bytes, + gas, + }, + address, + }), + _ => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::new(), + gas, + }, + address: None, + }), + }, + ExecutionResult::Revert { output, .. } => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas, + }, + address: None, + }), + ExecutionResult::Halt { .. } => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), + gas, + }, + address: None, + }), + } + } + } - disallowed_mem_write(dest_offset, size, interpreter, ranges); - return - } - } + None + } - $(opcode::$opcode => { - // The destination offset of the operation. - let dest_offset = try_or_continue!(interpreter.stack().peek($offset_depth)).saturating_to::(); - - // The size of the data that will be copied. - let size = try_or_continue!(interpreter.stack().peek($size_depth)).saturating_to::(); - - // If none of the allowed ranges contain [dest_offset, dest_offset + size), - // memory outside of the expected ranges has been touched. If the opcode - // only reads from memory, this is okay as long as the memory is not expanded. - let fail_cond = !ranges.iter().any(|range| { - range.contains(&dest_offset) && - range.contains(&(dest_offset + size.saturating_sub(1))) - }) && ($writes || - [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| { - offset >= interpreter.shared_memory.len() as u64 - }) - ); - - // If the failure condition is met, set the output buffer to a revert string - // that gives information about the allowed ranges and revert. - if fail_cond { - disallowed_mem_write(dest_offset, size, interpreter, ranges); - return - } - })* - _ => () - } + // common create_end functionality for both legacy and EOF. + fn create_end_common( + &mut self, + ecx: &mut EvmContext, + mut outcome: CreateOutcome, + ) -> CreateOutcome + where + DB: DatabaseExt, + { + let ecx = &mut ecx.inner; + + // Clean up pranks + if let Some(prank) = &self.prank { + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.prank); } } - - // Check if the current opcode can write to memory, and if so, check if the memory - // being written to is registered as safe to modify. - mem_opcode_match!( - (CALLDATACOPY, 0, 2, true), - (CODECOPY, 0, 2, true), - (RETURNDATACOPY, 0, 2, true), - (EXTCODECOPY, 1, 3, true), - (CALLCODE, 5, 6, true), - (STATICCALL, 4, 5, true), - (DELEGATECALL, 4, 5, true), - (KECCAK256, 0, 1, false), - (LOG0, 0, 1, false), - (LOG1, 0, 1, false), - (LOG2, 0, 1, false), - (LOG3, 0, 1, false), - (LOG4, 0, 1, false), - (CREATE, 1, 2, false), - (CREATE2, 1, 2, false), - (RETURN, 0, 1, false), - (REVERT, 0, 1, false), - ) } - // Record writes with sstore (and sha3) if `StartMappingRecording` has been called - if let Some(mapping_slots) = &mut self.mapping_slots { - mapping::step(mapping_slots, interpreter); - } - } + // Clean up broadcasts + if let Some(broadcast) = &self.broadcast { + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; - fn log(&mut self, _context: &mut EvmContext, log: &Log) { - if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, log); + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } + } } - // Stores this log if `recordLogs` has been called - if let Some(storage_recorded_logs) = &mut self.recorded_logs { - storage_recorded_logs.push(Vm::Log { - topics: log.data.topics().to_vec(), - data: log.data.data.clone(), - emitter: log.address, - }); + // Handle expected reverts + if let Some(expected_revert) = &self.expected_revert { + if ecx.journaled_state.depth() <= expected_revert.depth && + matches!(expected_revert.kind, ExpectedRevertKind::Default) + { + let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match expect::handle_expect_revert( + true, + expected_revert.reason.as_deref(), + outcome.result.result, + outcome.result.output.clone(), + ) { + Ok((address, retdata)) => { + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + outcome + } + Err(err) => { + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + outcome + } + }; + } } - self.combined_logs.push(None); + // If `startStateDiffRecording` has been called, update the `reverted` status of the + // previous call depth's recorded accesses, if any + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + // The root call cannot be recorded. + if ecx.journaled_state.depth() > 0 { + let mut last_depth = + recorded_account_diffs_stack.pop().expect("missing CREATE account accesses"); + // Update the reverted status of all deeper calls if this call reverted, in + // accordance with EVM behavior + if outcome.result.is_revert() { + last_depth.iter_mut().for_each(|element| { + element.reverted = true; + element + .storageAccesses + .iter_mut() + .for_each(|storage_access| storage_access.reverted = true); + }) + } + let create_access = last_depth.first_mut().expect("empty AccountAccesses"); + // Assert that we're at the correct depth before recording post-create state + // changes. Depending on what depth the cheat was called at, there + // may not be any pending calls to update if execution has + // percolated up to a higher depth. + if create_access.depth == ecx.journaled_state.depth() { + debug_assert_eq!( + create_access.kind as u8, + crate::Vm::AccountAccessKind::Create as u8 + ); + if let Some(address) = outcome.address { + if let Ok((created_acc, _)) = + ecx.journaled_state.load_account(address, &mut ecx.db) + { + create_access.newBalance = created_acc.info.balance; + create_access.deployedCode = + created_acc.info.code.clone().unwrap_or_default().original_bytes(); + } + } + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the current + // depth, or push them back onto the pending vector if higher depths were not + // recorded. This preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.append(&mut last_depth); + } else { + recorded_account_diffs_stack.push(last_depth); + } + } + } + outcome } - fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { + pub fn call_with_executor( + &mut self, + ecx: &mut EvmContext, + call: &mut CallInputs, + executor: &mut impl CheatcodesExecutor, + ) -> Option { + let ecx_inner = &mut ecx.inner; let gas = Gas::new(call.gas_limit); // At the root call to test function or script `run()`/`setUp()` functions, we are // decreasing sender nonce to ensure that it matches on-chain nonce once we start // broadcasting. - if ecx.journaled_state.depth == 0 { - let sender = ecx.env.tx.caller; + if ecx_inner.journaled_state.depth == 0 { + let sender = ecx_inner.env.tx.caller; if sender != Config::DEFAULT_SENDER { - let account = match super::evm::journaled_account(ecx, sender) { + let account = match super::evm::journaled_account(ecx_inner, sender) { Ok(account) => account, Err(err) => { return Some(CallOutcome { @@ -1146,12 +1117,12 @@ impl Inspector for Cheatcodes { let prev = account.info.nonce; account.info.nonce = prev.saturating_sub(1); - debug!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); + trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); } } if call.target_address == CHEATCODE_ADDRESS { - return match self.apply_cheatcode(ecx, call) { + return match self.apply_cheatcode(ecx, call, executor) { Ok(retdata) => Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Return, @@ -1171,9 +1142,8 @@ impl Inspector for Cheatcodes { }; } - if call.bytecode_address == HARDHAT_CONSOLE_ADDRESS { + if call.target_address == HARDHAT_CONSOLE_ADDRESS { self.combined_logs.push(None); - return None; } @@ -1223,25 +1193,25 @@ impl Inspector for Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), - }) + }); } } // Apply our prank if let Some(prank) = &self.prank { - if ecx.inner.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller + if ecx_inner.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { let mut prank_applied = false; // At the target depth we set `msg.sender` - if ecx.inner.journaled_state.depth() == prank.depth { + if ecx_inner.journaled_state.depth() == prank.depth { call.caller = prank.new_caller; prank_applied = true; } // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - ecx.inner.env.tx.caller = new_origin; + ecx_inner.env.tx.caller = new_origin; prank_applied = true; } @@ -1260,13 +1230,13 @@ impl Inspector for Cheatcodes { // // We do this because any subsequent contract calls *must* exist on chain and // we only want to grab *this* call, not internal ones - if ecx.inner.journaled_state.depth() == broadcast.depth && + if ecx_inner.journaled_state.depth() == broadcast.depth && call.caller == broadcast.original_caller { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the // broadcast.origin. - ecx.inner.env.tx.caller = broadcast.new_origin; + ecx_inner.env.tx.caller = broadcast.new_origin; call.caller = broadcast.new_origin; // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here @@ -1274,7 +1244,7 @@ impl Inspector for Cheatcodes { // into 1559, in the cli package, relatively easily once we // know the target chain supports EIP-1559. if !call.is_static { - if let Err(err) = ecx.inner.load_account(broadcast.new_origin) { + if let Err(err) = ecx_inner.load_account(broadcast.new_origin) { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1285,19 +1255,19 @@ impl Inspector for Cheatcodes { }) } - let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx.inner, call.gas_limit); + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx_inner, call.gas_limit); let account = - ecx.inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); let nonce = if self.use_zk_vm { - foundry_zksync_core::nonce(broadcast.new_origin, ecx) as u64 + foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64 } else { account.info.nonce }; let account = - ecx.inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + ecx_inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); let zk_tx = if self.use_zk_vm { // We shouldn't need factory_deps for CALLs @@ -1307,7 +1277,7 @@ impl Inspector for Cheatcodes { }; self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.inner.db.active_fork_url(), + rpc: ecx_inner.db.active_fork_url(), transaction: TransactionRequest { from: Some(broadcast.new_origin), to: Some(TxKind::from(Some(call.target_address))), @@ -1332,7 +1302,8 @@ impl Inspector for Cheatcodes { account.info.nonce += 1; debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } else if broadcast.single_call { - let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; + let msg = + "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1340,7 +1311,7 @@ impl Inspector for Cheatcodes { gas, }, memory_offset: call.return_memory_offset.clone(), - }) + }); } } } @@ -1351,10 +1322,7 @@ impl Inspector for Cheatcodes { // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code let initialized; let old_balance; - // TODO: use ecx.load_account - if let Ok((acc, _)) = - ecx.inner.journaled_state.load_account(call.target_address, &mut ecx.inner.db) - { + if let Ok((acc, _)) = ecx.load_account(call.target_address) { initialized = acc.info.exists(); old_balance = acc.info.balance; } else { @@ -1366,6 +1334,9 @@ impl Inspector for Cheatcodes { CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall, + CallScheme::ExtCall => crate::Vm::AccountAccessKind::Call, + CallScheme::ExtStaticCall => crate::Vm::AccountAccessKind::StaticCall, + CallScheme::ExtDelegateCall => crate::Vm::AccountAccessKind::DelegateCall, }; // Record this call by pushing it to a new pending vector; all subsequent calls at // that depth will be pushed to the same vector. When the call ends, the @@ -1374,8 +1345,8 @@ impl Inspector for Cheatcodes { // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.inner.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.inner.env.cfg.chain_id), + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, accessor: call.caller, account: call.bytecode_address, @@ -1388,7 +1359,7 @@ impl Inspector for Cheatcodes { reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step - depth: ecx.inner.journaled_state.depth(), + depth: ecx.journaled_state.depth(), }]); } @@ -1458,47 +1429,151 @@ impl Inspector for Cheatcodes { None } +} - fn call_end( - &mut self, - ecx: &mut EvmContext, - call: &CallInputs, - mut outcome: CallOutcome, - ) -> CallOutcome { - let ecx = &mut ecx.inner; - let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || - call.target_address == HARDHAT_CONSOLE_ADDRESS; +impl Inspector for Cheatcodes { + #[inline] + fn initialize_interp(&mut self, _interpreter: &mut Interpreter, ecx: &mut EvmContext) { + // When the first interpreter is initialized we've circumvented the balance and gas checks, + // so we apply our actual block data with the correct fees and all. + if let Some(block) = self.block.take() { + ecx.env.block = block; + } + if let Some(gas_price) = self.gas_price.take() { + ecx.env.tx.gas_price = gas_price; + } + if self.startup_zk && !self.use_zk_vm { + self.startup_zk = false; // We only do this once. + self.select_zk_vm(ecx, None); + } + } - // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do - // it for cheatcode calls because they are not appplied for cheatcodes in the `call` hook. - // This should be placed before the revert handling, because we might exit early there - if !cheatcode_call { - // Clean up pranks - if let Some(prank) = &self.prank { - if ecx.journaled_state.depth() == prank.depth { - ecx.env.tx.caller = prank.prank_origin; + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.pc = interpreter.program_counter(); - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - let _ = self.prank.take(); - } - } - } + // `pauseGasMetering`: reset interpreter gas. + if self.gas_metering.is_some() { + self.meter_gas(interpreter); + } - // Clean up broadcast - if let Some(broadcast) = &self.broadcast { - if ecx.journaled_state.depth() == broadcast.depth { - ecx.env.tx.caller = broadcast.original_origin; + // `record`: record storage reads and writes. + if self.accesses.is_some() { + self.record_accesses(interpreter); + } - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - let _ = self.broadcast.take(); - } - } - } + // `startStateDiffRecording`: record granular ordered storage accesses. + if self.recorded_account_diffs_stack.is_some() { + self.record_state_diffs(interpreter, ecx); } - // Handle expected reverts + // `expectSafeMemory`: check if the current opcode is allowed to interact with memory. + if !self.allowed_mem_writes.is_empty() { + self.check_mem_opcodes(interpreter, ecx.journaled_state.depth()); + } + + // `startMappingRecording`: record SSTORE and KECCAK256. + if let Some(mapping_slots) = &mut self.mapping_slots { + mapping::step(mapping_slots, interpreter); + } + } + + #[inline] + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + // override address(x).balance retrieval to make it consistent between EraVM and EVM + if self.use_zk_vm { + let address = match interpreter.current_opcode() { + opcode::SELFBALANCE => interpreter.contract().target_address, + opcode::BALANCE => { + if interpreter.stack.is_empty() { + interpreter.instruction_result = InstructionResult::StackUnderflow; + return; + } + + Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() })) + } + _ => return, + }; + + // Safety: Length is checked above. + let balance = foundry_zksync_core::balance(address, ecx); + + // Skip the current BALANCE instruction since we've already handled it + match interpreter.stack.push(balance) { + Ok(_) => unsafe { + interpreter.instruction_pointer = interpreter.instruction_pointer.add(1); + }, + Err(e) => { + interpreter.instruction_result = e; + } + } + } + } + + fn log(&mut self, _interpreter: &mut Interpreter, _context: &mut EvmContext, log: &Log) { + if !self.expected_emits.is_empty() { + expect::handle_expect_emit(self, log); + } + + // `recordLogs` + if let Some(storage_recorded_logs) = &mut self.recorded_logs { + storage_recorded_logs.push(Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + }); + } + self.combined_logs.push(None); + } + + fn call( + &mut self, + context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + Self::call_with_executor(self, context, inputs, &mut TransparentCheatcodesExecutor) + } + + fn call_end( + &mut self, + ecx: &mut EvmContext, + call: &CallInputs, + mut outcome: CallOutcome, + ) -> CallOutcome { + let ecx = &mut ecx.inner; + let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || + call.target_address == HARDHAT_CONSOLE_ADDRESS; + + // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do + // it for cheatcode calls because they are not appplied for cheatcodes in the `call` hook. + // This should be placed before the revert handling, because we might exit early there + if !cheatcode_call { + // Clean up pranks + if let Some(prank) = &self.prank { + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + let _ = self.prank.take(); + } + } + } + + // Clean up broadcast + if let Some(broadcast) = &self.broadcast { + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; + + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + let _ = self.broadcast.take(); + } + } + } + } + + // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { if ecx.journaled_state.depth() <= expected_revert.depth { let needs_processing: bool = match expected_revert.kind { @@ -1547,7 +1622,7 @@ impl Inspector for Cheatcodes { // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode // invocations if cheatcode_call { - return outcome + return outcome; } // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to @@ -1584,10 +1659,7 @@ impl Inspector for Cheatcodes { // Depending on the depth the cheat was called at, there may not be any pending // calls to update if execution has percolated up to a higher depth. if call_access.depth == ecx.journaled_state.depth() { - // TODO: use ecx.load_account - if let Ok((acc, _)) = - ecx.journaled_state.load_account(call.target_address, &mut ecx.db) - { + if let Ok((acc, _)) = ecx.load_account(call.target_address) { debug_assert!(access_is_call(call_access.kind)); call_access.newBalance = acc.info.balance; } @@ -1625,7 +1697,7 @@ impl Inspector for Cheatcodes { if self.expected_emits.iter().any(|expected| !expected.found) { outcome.result.result = InstructionResult::Revert; outcome.result.output = "log != expected log".abi_encode().into(); - return outcome + return outcome; } else { // All emits were found, we're good. // Clear the queue, as we expect the user to declare more events for the next call @@ -1643,13 +1715,13 @@ impl Inspector for Cheatcodes { if outcome.result.is_revert() { if let Some(err) = diag { outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return outcome + return outcome; } } // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TransactTo::Call(test_contract) = ecx.env.tx.transact_to { + if let TxKind::Call(test_contract) = ecx.env.tx.transact_to { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork if ecx.db.is_forked_mode() && @@ -1746,369 +1818,457 @@ impl Inspector for Cheatcodes { ecx: &mut EvmContext, call: &mut CreateInputs, ) -> Option { - let gas = Gas::new(call.gas_limit); - - // Apply our prank - if let Some(prank) = &self.prank { - if ecx.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { - // At the target depth we set `msg.sender` - if ecx.journaled_state.depth() == prank.depth { - call.caller = prank.new_caller; - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - ecx.env.tx.caller = new_origin; - } - } - } - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - if ecx.journaled_state.depth() >= broadcast.depth && - call.caller == broadcast.original_caller - { - if let Err(err) = ecx.load_account(broadcast.new_origin) { - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Error::encode(err), - gas, - }, - address: None, - }) - } - - ecx.env.tx.caller = broadcast.new_origin; - - if ecx.journaled_state.depth() == broadcast.depth { - call.caller = broadcast.new_origin; - let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); + self.create_common(ecx, call) + } - let account = &ecx.journaled_state.state()[&broadcast.new_origin]; - let mut to = None; - let mut nonce = account.info.nonce; - let mut call_init_code = call.init_code.clone(); + fn create_end( + &mut self, + ecx: &mut EvmContext, + _call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.create_end_common(ecx, outcome) + } - let mut zk_tx = if self.use_zk_vm { - to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); - nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx) as u64; - let contract = self - .dual_compiled_contracts - .find_by_evm_bytecode(&call.init_code.0) - .unwrap_or_else(|| { - panic!("failed finding contract for {:?}", call.init_code) - }); - let factory_deps = - self.dual_compiled_contracts.fetch_all_factory_deps(contract); + fn eofcreate( + &mut self, + ecx: &mut EvmContext, + call: &mut EOFCreateInputs, + ) -> Option { + self.create_common(ecx, call) + } - let constructor_input = - call.init_code[contract.evm_bytecode.len()..].to_vec(); + fn eofcreate_end( + &mut self, + ecx: &mut EvmContext, + _call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.create_end_common(ecx, outcome) + } +} - let create_input = foundry_zksync_core::encode_create_params( - &call.scheme, - contract.zk_bytecode_hash, - constructor_input, - ); - call_init_code = Bytes::from(create_input); +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> bool { + if let CreateScheme::Create2 { .. } = inputs.scheme { + let target_depth = if let Some(prank) = &self.prank { + prank.depth + } else if let Some(broadcast) = &self.broadcast { + broadcast.depth + } else { + 1 + }; - Some(factory_deps) - } else { - None - }; + ecx.journaled_state.depth() == target_depth && + (self.broadcast.is_some() || self.config.always_use_create_2_factory) + } else { + false + } + } +} - let rpc = ecx.db.active_fork_url(); - if let Some(factory_deps) = zk_tx { - let mut batched = - foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); - debug!(batches = batched.len(), "splitting factory deps for broadcast"); - // the last batch is the final one that does the deployment - zk_tx = batched.pop(); +impl Cheatcodes { + #[cold] + fn meter_gas(&mut self, interpreter: &mut Interpreter) { + match &self.gas_metering { + None => {} + // Need to store gas metering. + Some(None) => self.gas_metering = Some(Some(interpreter.gas)), + Some(Some(gas)) => { + match interpreter.current_opcode() { + opcode::CREATE | opcode::CREATE2 => { + // Set we're about to enter CREATE frame to meter its gas on first opcode + // inside it. + self.gas_metering_create = Some(None) + } + opcode::STOP | opcode::RETURN | opcode::SELFDESTRUCT | opcode::REVERT => { + match &self.gas_metering_create { + None | Some(None) => { + // If we are ending current execution frame, we want to reset + // interpreter gas to the value of gas spent during frame, so only + // the consumed gas is erased. + // ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/evm_impl.rs#L190 + // + // It would be nice if we had access to the interpreter in + // `call_end`, as we could just do this there instead. + interpreter.gas = Gas::new(interpreter.gas.spent()); - for factory_deps in batched { - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: rpc.clone(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(TxKind::Call(Address::ZERO)), - value: Some(call.value), - nonce: Some(nonce), - ..Default::default() - }, - zk_tx: Some(ZkTransactionMetadata { factory_deps }), - }); + // Make sure CREATE gas metering is resetted. + self.gas_metering_create = None + } + Some(Some(gas)) => { + // If this was CREATE frame, set correct gas limit. This is needed + // because CREATE opcodes deduct additional gas for code storage, + // and deducted amount is compared to gas limit. If we set this to + // 0, the CREATE would fail with out of gas. + // + // If we however set gas limit to the limit of outer frame, it would + // cause a panic after erasing gas cost post-create. Reason for this + // is pre-create REVM records `gas_limit - (gas_limit / 64)` as gas + // used, and erases costs by `remaining` gas post-create. + // gas used ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/instructions/host.rs#L254-L258 + // post-create erase ref: https://github.com/bluealloy/revm/blob/2cb991091d32330cfe085320891737186947ce5a/crates/revm/src/instructions/host.rs#L279 + interpreter.gas = Gas::new(gas.limit()); - //update nonce for each tx - nonce += 1; + // Reset CREATE gas metering because we're about to exit its frame. + self.gas_metering_create = None + } } } + _ => { + // If just starting with CREATE opcodes, record its inner frame gas. + if self.gas_metering_create == Some(None) { + self.gas_metering_create = Some(Some(interpreter.gas)) + } - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: rpc.clone(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to, - value: Some(call.value), - input: TransactionInput::new(call_init_code), - nonce: Some(nonce), - gas: if is_fixed_gas_limit { - Some(call.gas_limit as u128) - } else { - None - }, - ..Default::default() - }, - zk_tx: zk_tx.map(ZkTransactionMetadata::new), - }); - - let kind = match call.scheme { - CreateScheme::Create => "create", - CreateScheme::Create2 { .. } => "create2", - }; - debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); + // Don't monitor gas changes, keep it constant. + interpreter.gas = *gas; + } } } } + } - // allow cheatcodes from the address of the new contract - // Compute the address *after* any possible broadcast updates, so it's based on the updated - // call inputs - let address = self.allow_cheatcodes_on_create(ecx, call); - // If `recordAccountAccesses` has been called, record the create - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - // Record the create context as an account access and create a new vector to record all - // subsequent account accesses - recorded_account_diffs_stack.push(vec![AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), - }, - accessor: call.caller, - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on create_end - newBalance: U256::ZERO, // updated on create_end - value: call.value, - data: call.init_code.clone(), - reverted: false, - deployedCode: Bytes::new(), // updated on create_end - storageAccesses: vec![], // updated on create_end - depth: ecx.journaled_state.depth(), - }]); - } - - if self.use_zk_vm { - info!("running create in zk vm"); - if call.init_code.0 == DEFAULT_CREATE2_DEPLOYER_CODE { - info!("ignoring DEFAULT_CREATE2_DEPLOYER_CODE for zk"); - return None + /// Records storage slots reads and writes. + #[cold] + fn record_accesses(&mut self, interpreter: &mut Interpreter) { + let Some(access) = &mut self.accesses else { return }; + match interpreter.current_opcode() { + opcode::SLOAD => { + let key = try_or_return!(interpreter.stack().peek(0)); + access.record_read(interpreter.contract().target_address, key); } - - let zk_contract = self - .dual_compiled_contracts - .find_by_evm_bytecode(&call.init_code.0) - .unwrap_or_else(|| panic!("failed finding contract for {:?}", call.init_code)); - - let factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(zk_contract); - tracing::debug!(contract = zk_contract.name, "using dual compiled contract"); - - let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { - mocked_calls: self.mocked_calls.clone(), - expected_calls: Some(&mut self.expected_calls), - accesses: self.accesses.as_mut(), - persisted_factory_deps: Some(&mut self.persisted_factory_deps), - }; - if let Ok(result) = foundry_zksync_core::vm::create::<_, DatabaseError>( - call, - zk_contract, - factory_deps, - ecx, - ccx, - ) { - self.combined_logs.extend(result.logs.clone().into_iter().map(Some)); - - // for each log in cloned logs call handle_expect_emit - if !self.expected_emits.is_empty() { - for log in result.logs { - expect::handle_expect_emit(self, &log); - } - } - - return match result.execution_result { - ExecutionResult::Success { output, .. } => match output { - Output::Create(bytes, address) => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Return, - output: bytes, - gas, - }, - address, - }), - _ => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::new(), - gas, - }, - address: None, - }), - }, - ExecutionResult::Revert { output, .. } => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas, - }, - address: None, - }), - ExecutionResult::Halt { .. } => Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), - gas, - }, - address: None, - }), - } + opcode::SSTORE => { + let key = try_or_return!(interpreter.stack().peek(0)); + access.record_write(interpreter.contract().target_address, key); } + _ => {} } - - None } - fn create_end( + #[cold] + fn record_state_diffs( &mut self, + interpreter: &mut Interpreter, ecx: &mut EvmContext, - _call: &CreateInputs, - mut outcome: CreateOutcome, - ) -> CreateOutcome { - let ecx = &mut ecx.inner; + ) { + let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; + match interpreter.current_opcode() { + opcode::SELFDESTRUCT => { + // Ensure that we're not selfdestructing a context recording was initiated on + let Some(last) = account_accesses.last_mut() else { return }; - // Clean up pranks - if let Some(prank) = &self.prank { - if ecx.journaled_state.depth() == prank.depth { - ecx.env.tx.caller = prank.prank_origin; + // get previous balance and initialized status of the target account + let target = try_or_return!(interpreter.stack().peek(0)); + let target = Address::from_word(B256::from(target)); + let (initialized, old_balance) = ecx + .load_account(target) + .map(|(account, _)| (account.info.exists(), account.info.balance)) + .unwrap_or_default(); - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - std::mem::take(&mut self.prank); - } + // load balance of this account + let value = ecx + .balance(interpreter.contract().target_address) + .map(|(b, _)| b) + .unwrap_or(U256::ZERO); + + // register access for the target account + last.push(crate::Vm::AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), + }, + accessor: interpreter.contract().target_address, + account: target, + kind: crate::Vm::AccountAccessKind::SelfDestruct, + initialized, + oldBalance: old_balance, + newBalance: old_balance + value, + value, + data: Bytes::new(), + reverted: false, + deployedCode: Bytes::new(), + storageAccesses: vec![], + depth: ecx.journaled_state.depth(), + }); } - } - // Clean up broadcasts - if let Some(broadcast) = &self.broadcast { - if ecx.journaled_state.depth() == broadcast.depth { - ecx.env.tx.caller = broadcast.original_origin; + opcode::SLOAD => { + let Some(last) = account_accesses.last_mut() else { return }; - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - } + let key = try_or_return!(interpreter.stack().peek(0)); + let address = interpreter.contract().target_address; - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if ecx.journaled_state.depth() <= expected_revert.depth && - matches!(expected_revert.kind, ExpectedRevertKind::Default) - { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match expect::handle_expect_revert( - true, - expected_revert.reason.as_deref(), - outcome.result.result, - outcome.result.output.clone(), - ) { - Ok((address, retdata)) => { - outcome.result.result = InstructionResult::Return; - outcome.result.output = retdata; - outcome.address = address; - outcome + // Try to include present value for informational purposes, otherwise assume + // it's not set (zero value) + let mut present_value = U256::ZERO; + // Try to load the account and the slot's present value + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { + present_value = previous; } - Err(err) => { - outcome.result.result = InstructionResult::Revert; - outcome.result.output = err.abi_encode().into(); - outcome + } + let access = crate::Vm::StorageAccess { + account: interpreter.contract().target_address, + slot: key.into(), + isWrite: false, + previousValue: present_value.into(), + newValue: present_value.into(), + reverted: false, + }; + append_storage_access(last, access, ecx.journaled_state.depth()); + } + opcode::SSTORE => { + let Some(last) = account_accesses.last_mut() else { return }; + + let key = try_or_return!(interpreter.stack().peek(0)); + let value = try_or_return!(interpreter.stack().peek(1)); + let address = interpreter.contract().target_address; + // Try to load the account and the slot's previous value, otherwise, assume it's + // not set (zero value) + let mut previous_value = U256::ZERO; + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { + previous_value = previous; } + } + + let access = crate::Vm::StorageAccess { + account: address, + slot: key.into(), + isWrite: true, + previousValue: previous_value.into(), + newValue: value.into(), + reverted: false, }; + append_storage_access(last, access, ecx.journaled_state.depth()); } - } - // If `startStateDiffRecording` has been called, update the `reverted` status of the - // previous call depth's recorded accesses, if any - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - // The root call cannot be recorded. - if ecx.journaled_state.depth() > 0 { - let mut last_depth = - recorded_account_diffs_stack.pop().expect("missing CREATE account accesses"); - // Update the reverted status of all deeper calls if this call reverted, in - // accordance with EVM behavior - if outcome.result.is_revert() { - last_depth.iter_mut().for_each(|element| { - element.reverted = true; - element - .storageAccesses - .iter_mut() - .for_each(|storage_access| storage_access.reverted = true); - }) - } - let create_access = last_depth.first_mut().expect("empty AccountAccesses"); - // Assert that we're at the correct depth before recording post-create state - // changes. Depending on what depth the cheat was called at, there - // may not be any pending calls to update if execution has - // percolated up to a higher depth. - if create_access.depth == ecx.journaled_state.depth() { - debug_assert_eq!( - create_access.kind as u8, - crate::Vm::AccountAccessKind::Create as u8 - ); - if let Some(address) = outcome.address { - if let Ok((created_acc, _)) = - ecx.journaled_state.load_account(address, &mut ecx.db) - { - create_access.newBalance = created_acc.info.balance; - create_access.deployedCode = - created_acc.info.code.clone().unwrap_or_default().original_bytes(); - } - } + // Record account accesses via the EXT family of opcodes + opcode::EXTCODECOPY | opcode::EXTCODESIZE | opcode::EXTCODEHASH | opcode::BALANCE => { + let kind = match interpreter.current_opcode() { + opcode::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, + opcode::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, + opcode::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, + opcode::BALANCE => crate::Vm::AccountAccessKind::Balance, + _ => unreachable!(), + }; + let address = + Address::from_word(B256::from(try_or_return!(interpreter.stack().peek(0)))); + let initialized; + let balance; + if let Ok((acc, _)) = ecx.load_account(address) { + initialized = acc.info.exists(); + balance = acc.info.balance; + } else { + initialized = false; + balance = U256::ZERO; } - // Merge the last depth's AccountAccesses into the AccountAccesses at the current - // depth, or push them back onto the pending vector if higher depths were not - // recorded. This preserves ordering of accesses. - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.append(&mut last_depth); + let account_access = crate::Vm::AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), + }, + accessor: interpreter.contract().target_address, + account: address, + kind, + initialized, + oldBalance: balance, + newBalance: balance, + value: U256::ZERO, + data: Bytes::new(), + reverted: false, + deployedCode: Bytes::new(), + storageAccesses: vec![], + depth: ecx.journaled_state.depth(), + }; + // Record the EXT* call as an account access at the current depth + // (future storage accesses will be recorded in a new "Resume" context) + if let Some(last) = account_accesses.last_mut() { + last.push(account_access); } else { - recorded_account_diffs_stack.push(last_depth); + account_accesses.push(vec![account_access]); } } + _ => {} } - - outcome } -} -impl InspectorExt for Cheatcodes { - fn should_use_create2_factory( - &mut self, - ecx: &mut EvmContext, - inputs: &mut CreateInputs, - ) -> bool { - if let CreateScheme::Create2 { .. } = inputs.scheme { - let target_depth = if let Some(prank) = &self.prank { - prank.depth - } else if let Some(broadcast) = &self.broadcast { - broadcast.depth - } else { - 1 - }; + /// Checks to see if the current opcode can either mutate directly or expand memory. + /// + /// If the opcode at the current program counter is a match, check if the modified memory lies + /// within the allowed ranges. If not, revert and fail the test. + #[cold] + fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) { + let Some(ranges) = self.allowed_mem_writes.get(&depth) else { + return; + }; - ecx.journaled_state.depth() == target_depth && - (self.broadcast.is_some() || self.config.always_use_create_2_factory) - } else { - false + // The `mem_opcode_match` macro is used to match the current opcode against a list of + // opcodes that can mutate memory (either directly or expansion via reading). If the + // opcode is a match, the memory offsets that are being written to are checked to be + // within the allowed ranges. If not, the test is failed and the transaction is + // reverted. For all opcodes that can mutate memory aside from MSTORE, + // MSTORE8, and MLOAD, the size and destination offset are on the stack, and + // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the + // size of the memory write is implicit, so these cases are hard-coded. + macro_rules! mem_opcode_match { + ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => { + match interpreter.current_opcode() { + //////////////////////////////////////////////////////////////// + // OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING // + //////////////////////////////////////////////////////////////// + + opcode::MSTORE => { + // The offset of the mstore operation is at the top of the stack. + let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + + // If none of the allowed ranges contain [offset, offset + 32), memory has been + // unexpectedly mutated. + if !ranges.iter().any(|range| { + range.contains(&offset) && range.contains(&(offset + 31)) + }) { + // SPECIAL CASE: When the compiler attempts to store the selector for + // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory + // pointer, which could have been updated to the exclusive upper bound during + // execution. + let value = try_or_return!(interpreter.stack().peek(1)).to_be_bytes::<32>(); + if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { + return + } + + disallowed_mem_write(offset, 32, interpreter, ranges); + return + } + } + opcode::MSTORE8 => { + // The offset of the mstore8 operation is at the top of the stack. + let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + + // If none of the allowed ranges contain the offset, memory has been + // unexpectedly mutated. + if !ranges.iter().any(|range| range.contains(&offset)) { + disallowed_mem_write(offset, 1, interpreter, ranges); + return + } + } + + //////////////////////////////////////////////////////////////// + // OPERATIONS THAT CAN EXPAND MEMORY BY READING // + //////////////////////////////////////////////////////////////// + + opcode::MLOAD => { + // The offset of the mload operation is at the top of the stack + let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + + // If the offset being loaded is >= than the memory size, the + // memory is being expanded. If none of the allowed ranges contain + // [offset, offset + 32), memory has been unexpectedly mutated. + if offset >= interpreter.shared_memory.len() as u64 && !ranges.iter().any(|range| { + range.contains(&offset) && range.contains(&(offset + 31)) + }) { + disallowed_mem_write(offset, 32, interpreter, ranges); + return + } + } + + //////////////////////////////////////////////////////////////// + // OPERATIONS WITH OFFSET AND SIZE ON STACK // + //////////////////////////////////////////////////////////////// + + opcode::CALL => { + // The destination offset of the operation is the fifth element on the stack. + let dest_offset = try_or_return!(interpreter.stack().peek(5)).saturating_to::(); + + // The size of the data that will be copied is the sixth element on the stack. + let size = try_or_return!(interpreter.stack().peek(6)).saturating_to::(); + + // If none of the allowed ranges contain [dest_offset, dest_offset + size), + // memory outside of the expected ranges has been touched. If the opcode + // only reads from memory, this is okay as long as the memory is not expanded. + let fail_cond = !ranges.iter().any(|range| { + range.contains(&dest_offset) && + range.contains(&(dest_offset + size.saturating_sub(1))) + }); + + // If the failure condition is met, set the output buffer to a revert string + // that gives information about the allowed ranges and revert. + if fail_cond { + // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed. + // It allocated calldata at the current free memory pointer, and will attempt to read + // from this memory region to perform the call. + let to = Address::from_word(try_or_return!(interpreter.stack().peek(1)).to_be_bytes::<32>().into()); + if to == CHEATCODE_ADDRESS { + let args_offset = try_or_return!(interpreter.stack().peek(3)).saturating_to::(); + let args_size = try_or_return!(interpreter.stack().peek(4)).saturating_to::(); + let memory_word = interpreter.shared_memory.slice(args_offset, args_size); + if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { + return + } + } + + disallowed_mem_write(dest_offset, size, interpreter, ranges); + return + } + } + + $(opcode::$opcode => { + // The destination offset of the operation. + let dest_offset = try_or_return!(interpreter.stack().peek($offset_depth)).saturating_to::(); + + // The size of the data that will be copied. + let size = try_or_return!(interpreter.stack().peek($size_depth)).saturating_to::(); + + // If none of the allowed ranges contain [dest_offset, dest_offset + size), + // memory outside of the expected ranges has been touched. If the opcode + // only reads from memory, this is okay as long as the memory is not expanded. + let fail_cond = !ranges.iter().any(|range| { + range.contains(&dest_offset) && + range.contains(&(dest_offset + size.saturating_sub(1))) + }) && ($writes || + [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| { + offset >= interpreter.shared_memory.len() as u64 + }) + ); + + // If the failure condition is met, set the output buffer to a revert string + // that gives information about the allowed ranges and revert. + if fail_cond { + disallowed_mem_write(dest_offset, size, interpreter, ranges); + return + } + })* + + _ => {} + } + } } + + // Check if the current opcode can write to memory, and if so, check if the memory + // being written to is registered as safe to modify. + mem_opcode_match!( + (CALLDATACOPY, 0, 2, true), + (CODECOPY, 0, 2, true), + (RETURNDATACOPY, 0, 2, true), + (EXTCODECOPY, 1, 3, true), + (CALLCODE, 5, 6, true), + (STATICCALL, 4, 5, true), + (DELEGATECALL, 4, 5, true), + (KECCAK256, 0, 1, false), + (LOG0, 0, 1, false), + (LOG1, 0, 1, false), + (LOG2, 0, 1, false), + (LOG3, 0, 1, false), + (LOG4, 0, 1, false), + (CREATE, 1, 2, false), + (CREATE2, 1, 2, false), + (RETURN, 0, 1, false), + (REVERT, 0, 1, false), + ); } } @@ -2157,18 +2317,6 @@ fn check_if_fixed_gas_limit( && call_gas_limit > 2300 } -/// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch(calls: &Vm::VmCalls, ccx: &mut CheatsCtxt) -> Result { - macro_rules! match_ { - ($($variant:ident),*) => { - match calls { - $(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_traced(cheat, ccx),)* - } - }; - } - vm_calls!(match_) -} - /// Returns true if the kind of account access is a call. fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { matches!( @@ -2182,48 +2330,97 @@ fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { /// Appends an AccountAccess that resumes the recording of the current context. fn append_storage_access( - accesses: &mut [Vec], + last: &mut Vec, storage_access: crate::Vm::StorageAccess, storage_depth: u64, ) { - if let Some(last) = accesses.last_mut() { - // Assert that there's an existing record for the current context. - if !last.is_empty() && last.first().unwrap().depth < storage_depth { - // Three cases to consider: - // 1. If there hasn't been a context switch since the start of this context, then add - // the storage access to the current context record. - // 2. If there's an existing Resume record, then add the storage access to it. - // 3. Otherwise, create a new Resume record based on the current context. - if last.len() == 1 { - last.first_mut().unwrap().storageAccesses.push(storage_access); + // Assert that there's an existing record for the current context. + if !last.is_empty() && last.first().unwrap().depth < storage_depth { + // Three cases to consider: + // 1. If there hasn't been a context switch since the start of this context, then add the + // storage access to the current context record. + // 2. If there's an existing Resume record, then add the storage access to it. + // 3. Otherwise, create a new Resume record based on the current context. + if last.len() == 1 { + last.first_mut().unwrap().storageAccesses.push(storage_access); + } else { + let last_record = last.last_mut().unwrap(); + if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { + last_record.storageAccesses.push(storage_access); } else { - let last_record = last.last_mut().unwrap(); - if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { - last_record.storageAccesses.push(storage_access); - } else { - let entry = last.first().unwrap(); - let resume_record = crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: entry.chainInfo.forkId, - chainId: entry.chainInfo.chainId, - }, - accessor: entry.accessor, - account: entry.account, - kind: crate::Vm::AccountAccessKind::Resume, - initialized: entry.initialized, - storageAccesses: vec![storage_access], - reverted: entry.reverted, - // The remaining fields are defaults - oldBalance: U256::ZERO, - newBalance: U256::ZERO, - value: U256::ZERO, - data: Bytes::new(), - deployedCode: Bytes::new(), - depth: entry.depth, - }; - last.push(resume_record); - } + let entry = last.first().unwrap(); + let resume_record = crate::Vm::AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: entry.chainInfo.forkId, + chainId: entry.chainInfo.chainId, + }, + accessor: entry.accessor, + account: entry.account, + kind: crate::Vm::AccountAccessKind::Resume, + initialized: entry.initialized, + storageAccesses: vec![storage_access], + reverted: entry.reverted, + // The remaining fields are defaults + oldBalance: U256::ZERO, + newBalance: U256::ZERO, + value: U256::ZERO, + data: Bytes::new(), + deployedCode: Bytes::new(), + depth: entry.depth, + }; + last.push(resume_record); + } + } + } +} + +/// Dispatches the cheatcode call to the appropriate function. +fn apply_dispatch( + calls: &Vm::VmCalls, + ccx: &mut CheatsCtxt, + executor: &mut E, +) -> Result { + macro_rules! dispatch { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_full(cheat, ccx, executor),)* } + }; + } + + let _guard = trace_span_and_call(calls); + let result = vm_calls!(dispatch); + trace_return(&result); + result +} + +fn trace_span_and_call(calls: &Vm::VmCalls) -> tracing::span::EnteredSpan { + let mut cheat = None; + let mut get_cheat = || *cheat.get_or_insert_with(|| calls_as_dyn_cheatcode(calls)); + let span = debug_span!(target: "cheatcodes", "apply", id = %get_cheat().id()); + let entered = span.entered(); + trace!(target: "cheatcodes", cheat = ?get_cheat().as_debug(), "applying"); + entered +} + +fn trace_return(result: &Result) { + trace!( + target: "cheatcodes", + return = %match result { + Ok(b) => hex::encode(b), + Err(e) => e.to_string(), } + ); +} + +#[cold] +fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { + macro_rules! as_dyn { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => cheat,)* + } + }; } + vm_calls!(as_dyn) } diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs new file mode 100644 index 000000000..dfccd4b55 --- /dev/null +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -0,0 +1,111 @@ +use crate::inspector::Cheatcodes; +use alloy_primitives::{Address, Bytes, U256}; +use foundry_evm_core::backend::DatabaseExt; +use revm::{ + interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}, + InnerEvmContext, +}; + +/// Common behaviour of legacy and EOF create inputs. +pub(crate) trait CommonCreateInput { + fn caller(&self) -> Address; + fn gas_limit(&self) -> u64; + fn value(&self) -> U256; + fn init_code(&self) -> Bytes; + fn scheme(&self) -> Option; + fn set_caller(&mut self, caller: Address); + fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut InnerEvmContext, + ) -> Address; + fn computed_created_address(&self) -> Option
; +} + +impl CommonCreateInput for &mut CreateInputs { + fn caller(&self) -> Address { + self.caller + } + fn gas_limit(&self) -> u64 { + self.gas_limit + } + fn value(&self) -> U256 { + self.value + } + fn init_code(&self) -> Bytes { + self.init_code.clone() + } + fn scheme(&self) -> Option { + Some(self.scheme) + } + fn set_caller(&mut self, caller: Address) { + self.caller = caller; + } + fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme) { + let kind = match scheme { + CreateScheme::Create => "create", + CreateScheme::Create2 { .. } => "create2", + }; + debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); + } + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut InnerEvmContext, + ) -> Address { + let old_nonce = ecx + .journaled_state + .state + .get(&self.caller) + .map(|acc| acc.info.nonce) + .unwrap_or_default(); + let created_address = self.created_address(old_nonce); + cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); + created_address + } + fn computed_created_address(&self) -> Option
{ + None + } +} + +impl CommonCreateInput for &mut EOFCreateInputs { + fn caller(&self) -> Address { + self.caller + } + fn gas_limit(&self) -> u64 { + self.gas_limit + } + fn value(&self) -> U256 { + self.value + } + fn init_code(&self) -> Bytes { + match &self.kind { + EOFCreateKind::Tx { initdata } => initdata.clone(), + EOFCreateKind::Opcode { initcode, .. } => initcode.raw.clone(), + } + } + fn scheme(&self) -> Option { + None + } + fn set_caller(&mut self, caller: Address) { + self.caller = caller; + } + fn log_debug(&self, cheatcode: &mut Cheatcodes, _scheme: &CreateScheme) { + debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable eofcreate"); + } + fn allow_cheatcodes( + &self, + cheatcodes: &mut Cheatcodes, + ecx: &mut InnerEvmContext, + ) -> Address { + let created_address = + <&mut EOFCreateInputs as CommonCreateInput>::computed_created_address(self) + .unwrap_or_default(); + cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); + created_address + } + fn computed_created_address(&self) -> Option
{ + self.kind.created_address().copied() + } +} diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index d3c3e9596..48c14e2a3 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -1,13 +1,13 @@ //! Implementations of [`Json`](spec::Group::Json) cheatcodes. use crate::{string, Cheatcode, Cheatcodes, Result, Vm::*}; -use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{Address, B256, I256}; +use alloy_dyn_abi::{eip712_parser::EncodeType, DynSolType, DynSolValue, Resolver}; +use alloy_primitives::{hex, Address, B256, I256}; use alloy_sol_types::SolValue; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use serde_json::Value; -use std::{borrow::Cow, collections::BTreeMap, fmt::Write}; +use serde_json::{Map, Value}; +use std::{borrow::Cow, collections::BTreeMap}; impl Cheatcode for keyExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { @@ -47,7 +47,7 @@ impl Cheatcode for parseJsonUintCall { impl Cheatcode for parseJsonUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::Uint(256)) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } @@ -61,7 +61,7 @@ impl Cheatcode for parseJsonIntCall { impl Cheatcode for parseJsonIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::Int(256)) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } @@ -75,7 +75,7 @@ impl Cheatcode for parseJsonBoolCall { impl Cheatcode for parseJsonBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::Bool) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } @@ -89,7 +89,7 @@ impl Cheatcode for parseJsonAddressCall { impl Cheatcode for parseJsonAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::Address) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } @@ -103,7 +103,7 @@ impl Cheatcode for parseJsonStringCall { impl Cheatcode for parseJsonStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::String) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String))) } } @@ -117,7 +117,7 @@ impl Cheatcode for parseJsonBytesCall { impl Cheatcode for parseJsonBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::Bytes) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } @@ -131,7 +131,29 @@ impl Cheatcode for parseJsonBytes32Call { impl Cheatcode for parseJsonBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - parse_json_coerce(json, key, &DynSolType::FixedBytes(32)) + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) + } +} + +impl Cheatcode for parseJsonType_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, typeDescription } = self; + parse_json_coerce(json, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseJsonType_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key, typeDescription } = self; + parse_json_coerce(json, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseJsonTypeArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key, typeDescription } = self; + let ty = resolve_type(typeDescription)?; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) } } @@ -145,106 +167,162 @@ impl Cheatcode for parseJsonKeysCall { impl Cheatcode for serializeJsonCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, value } = self; - serialize_json(state, objectKey, None, value) + *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?; + Ok(value.abi_encode()) } } impl Cheatcode for serializeBool_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeUint_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeInt_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeAddress_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeBytes32_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32)) } } impl Cheatcode for serializeString_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), value) + serialize_json(state, objectKey, valueKey, value.clone().into()) } } impl Cheatcode for serializeBytes_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &hex::encode_prefixed(value)) + serialize_json(state, objectKey, valueKey, value.to_vec().into()) } } impl Cheatcode for serializeBool_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, false)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()), + ) } } impl Cheatcode for serializeUint_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, false)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()), + ) } } impl Cheatcode for serializeInt_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, false)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()), + ) } } impl Cheatcode for serializeAddress_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, true)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()), + ) } } impl Cheatcode for serializeBytes32_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, true)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()), + ) } } impl Cheatcode for serializeString_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - serialize_json(state, objectKey, Some(valueKey), &array_str(values, true)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()), + ) } } impl Cheatcode for serializeBytes_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; - let values = values.iter().map(hex::encode_prefixed); - serialize_json(state, objectKey, Some(valueKey), &array_str(values, true)) + serialize_json( + state, + objectKey, + valueKey, + DynSolValue::Array( + values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(), + ), + ) + } +} + +impl Cheatcode for serializeJsonType_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { typeDescription, value } = self; + let ty = resolve_type(typeDescription)?; + let value = ty.abi_decode(value)?; + let value = serialize_value_as_json(value)?; + Ok(value.to_string().abi_encode()) + } +} + +impl Cheatcode for serializeJsonType_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, typeDescription, value } = self; + let ty = resolve_type(typeDescription)?; + let value = ty.abi_decode(value)?; + serialize_json(state, objectKey, valueKey, value) } } @@ -252,7 +330,7 @@ impl Cheatcode for serializeUintToHexCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; let hex = format!("0x{value:x}"); - serialize_json(state, objectKey, Some(valueKey), &hex) + serialize_json(state, objectKey, valueKey, hex.into()) } } @@ -298,29 +376,75 @@ pub(super) fn parse_json(json: &str, path: &str) -> Result { } pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { - let value = parse_json_str(json)?; - let values = select(&value, path)?; - ensure!(!values.is_empty(), "no matching value found at {path:?}"); + let json = parse_json_str(json)?; + let [value] = select(&json, path)?[..] else { + bail!("path {path:?} must return exactly one JSON value"); + }; - ensure!( - values.iter().all(|value| !value.is_object()), - "values at {path:?} must not be JSON objects" - ); + parse_json_as(value, ty).map(|v| v.abi_encode()) +} +/// Parses given [serde_json::Value] as a [DynSolValue]. +pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result { let to_string = |v: &Value| { let mut s = v.to_string(); s.retain(|c: char| c != '"'); s }; - if let Some(array) = values[0].as_array() { - debug!(target: "cheatcodes", %ty, "parsing array"); - string::parse_array(array.iter().map(to_string), ty) - } else { - debug!(target: "cheatcodes", %ty, "parsing string"); - string::parse(&to_string(values[0]), ty) + + match (value, ty) { + (Value::Array(array), ty) => parse_json_array(array, ty), + (Value::Object(object), ty) => parse_json_map(object, ty), + (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())), + _ => string::parse_value(&to_string(value), ty), + } +} + +pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result { + match ty { + DynSolType::Tuple(types) => { + ensure!(array.len() == types.len(), "array length mismatch"); + let values = array + .iter() + .zip(types) + .map(|(e, ty)| parse_json_as(e, ty)) + .collect::>>()?; + + Ok(DynSolValue::Tuple(values)) + } + DynSolType::Array(inner) => { + let values = + array.iter().map(|e| parse_json_as(e, inner)).collect::>>()?; + Ok(DynSolValue::Array(values)) + } + DynSolType::FixedArray(inner, len) => { + ensure!(array.len() == *len, "array length mismatch"); + let values = + array.iter().map(|e| parse_json_as(e, inner)).collect::>>()?; + Ok(DynSolValue::FixedArray(values)) + } + _ => bail!("expected {ty}, found array"), } } +pub(super) fn parse_json_map(map: &Map, ty: &DynSolType) -> Result { + let Some((name, fields, types)) = ty.as_custom_struct() else { + bail!("expected {ty}, found JSON object"); + }; + + let mut values = Vec::with_capacity(fields.len()); + for (field, ty) in fields.iter().zip(types.iter()) { + let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") }; + values.push(parse_json_as(value, ty)?); + } + + Ok(DynSolValue::CustomStruct { + name: name.to_string(), + prop_names: fields.to_vec(), + tuple: values, + }) +} + pub(super) fn parse_json_keys(json: &str, key: &str) -> Result { let json = parse_json_str(json)?; let values = select(&json, key)?; @@ -376,7 +500,8 @@ pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { } } -/// Converts a JSON [`Value`] to a [`DynSolValue`]. +/// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer +/// decoding, use [`parse_json_as`]. /// /// The function is designed to run recursively, so that in case of an object /// it will call itself to convert each of it's value and encode the whole as a @@ -448,20 +573,63 @@ pub(super) fn json_value_to_token(value: &Value) -> Result { s = format!("0{val}"); val = &s[..]; } - let bytes = hex::decode(val)?; - Ok(match bytes.len() { - 20 => DynSolValue::Address(Address::from_slice(&bytes)), - 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32), - _ => DynSolValue::Bytes(bytes), - }) + if let Ok(bytes) = hex::decode(val) { + return Ok(match bytes.len() { + 20 => DynSolValue::Address(Address::from_slice(&bytes)), + 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32), + _ => DynSolValue::Bytes(bytes), + }); + } + } + Ok(DynSolValue::String(string.to_owned())) + } + } +} + +/// Serializes given [DynSolValue] into a [serde_json::Value]. +fn serialize_value_as_json(value: DynSolValue) -> Result { + match value { + DynSolValue::Bool(b) => Ok(Value::Bool(b)), + DynSolValue::String(s) => { + // Strings are allowed to contain strigified JSON objects, so we try to parse it like + // one first. + if let Ok(map) = serde_json::from_str(&s) { + Ok(Value::Object(map)) } else { - Ok(DynSolValue::String(string.to_owned())) + Ok(Value::String(s)) } } + DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))), + DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))), + DynSolValue::Int(i, _) => { + // let serde handle number parsing + let n = serde_json::from_str(&i.to_string())?; + Ok(Value::Number(n)) + } + DynSolValue::Uint(i, _) => { + // let serde handle number parsing + let n = serde_json::from_str(&i.to_string())?; + Ok(Value::Number(n)) + } + DynSolValue::Address(a) => Ok(Value::String(a.to_string())), + DynSolValue::Array(e) | DynSolValue::FixedArray(e) => { + Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::>()?)) + } + DynSolValue::CustomStruct { name: _, prop_names, tuple } => { + let values = + tuple.into_iter().map(serialize_value_as_json).collect::>>()?; + let map = prop_names.into_iter().zip(values).collect(); + + Ok(Value::Object(map)) + } + DynSolValue::Tuple(values) => Ok(Value::Array( + values.into_iter().map(serialize_value_as_json).collect::>()?, + )), + DynSolValue::Function(_) => bail!("cannot serialize function pointer"), } } -/// Serializes a key:value pair to a specific object. If the key is Some(valueKey), the value is +/// Serializes a key:value pair to a specific object. If the key is valueKey, the value is /// expected to be an object, which will be set as the root object for the provided object key, /// overriding the whole root object if the object key already exists. By calling this function /// multiple times, the user can serialize multiple KV pairs to the same object. The value can be of @@ -472,44 +640,99 @@ pub(super) fn json_value_to_token(value: &Value) -> Result { fn serialize_json( state: &mut Cheatcodes, object_key: &str, - value_key: Option<&str>, - value: &str, + value_key: &str, + value: DynSolValue, ) -> Result { + let value = serialize_value_as_json(value)?; let map = state.serialized_jsons.entry(object_key.into()).or_default(); - if let Some(value_key) = value_key { - let parsed_value = - serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.into())); - map.insert(value_key.into(), parsed_value); - } else { - *map = serde_json::from_str(value) - .map_err(|err| fmt_err!("failed to parse JSON object: {err}"))?; - } + map.insert(value_key.into(), value); let stringified = serde_json::to_string(map).unwrap(); Ok(stringified.abi_encode()) } -fn array_str(values: I, quoted: bool) -> String -where - I: IntoIterator, - I::IntoIter: ExactSizeIterator, - T: std::fmt::Display, -{ - let iter = values.into_iter(); - let mut s = String::with_capacity(2 + iter.len() * 32); - s.push('['); - for (i, item) in iter.enumerate() { - if i > 0 { - s.push(','); +/// Resolves a [DynSolType] from user input. +fn resolve_type(type_description: &str) -> Result { + if let Ok(ty) = DynSolType::parse(type_description) { + return Ok(ty); + }; + + if let Ok(encoded) = EncodeType::parse(type_description) { + let main_type = encoded.types[0].type_name; + let mut resolver = Resolver::default(); + for t in encoded.types { + resolver.ingest(t.to_owned()); + } + + return Ok(resolver.resolve(main_type)?) + }; + + bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string") +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::FixedBytes; + use proptest::strategy::Strategy; + + fn contains_tuple(value: &DynSolValue) -> bool { + match value { + DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => true, + DynSolValue::Array(v) | DynSolValue::FixedArray(v) => { + v.first().map_or(false, contains_tuple) + } + _ => false, + } + } + + /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and + /// [DynSolValue::Address] respectively. Thus, we can't distinguish between address and bytes of + /// length 20 during decoding. Because of that, there are issues with handling of arrays of + /// those types. + fn fixup_guessable(value: DynSolValue) -> DynSolValue { + match value { + DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => { + if let Some(DynSolValue::Bytes(_)) = v.first() { + v.retain(|v| { + let len = v.as_bytes().unwrap().len(); + len != 32 && len != 20 + }) + } + DynSolValue::Array(v.into_iter().map(fixup_guessable).collect()) + } + DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32), + DynSolValue::Bytes(v) if v.len() == 32 => { + DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32) + } + DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)), + _ => value, } + } + + fn guessable_types() -> impl proptest::strategy::Strategy { + proptest::arbitrary::any::() + .prop_map(fixup_guessable) + .prop_filter("tuples are not supported", |v| !contains_tuple(v)) + .prop_filter("filter out values without type", |v| v.as_type().is_some()) + } - if quoted { - s.push('"'); + // Tests to ensure that conversion [DynSolValue] -> [serde_json::Value] -> [DynSolValue] + proptest::proptest! { + #[test] + fn test_json_roundtrip_guessed(v in guessable_types()) { + let json = serialize_value_as_json(v.clone()).unwrap(); + let value = json_value_to_token(&json).unwrap(); + + // do additional abi_encode -> abi_decode to avoid zero signed integers getting decoded as unsigned and causing assert_eq to fail. + let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap(); + assert_eq!(decoded, v); } - write!(s, "{item}").unwrap(); - if quoted { - s.push('"'); + + #[test] + fn test_json_roundtrip(v in proptest::arbitrary::any::().prop_filter("filter out values without type", |v| v.as_type().is_some())) { + let json = serialize_value_as_json(v.clone()).unwrap(); + let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap(); + assert_eq!(value, v); } } - s.push(']'); - s } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index e18621d7c..3d9136fbf 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -17,7 +17,9 @@ use revm::{ContextPrecompiles, InnerEvmContext}; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; -pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; +pub use inspector::{ + BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, Context, +}; pub use spec::{CheatcodeDef, Vm}; pub use Vm::ForgeContext; @@ -45,8 +47,6 @@ pub use script::{ScriptWallets, ScriptWalletsInner}; mod string; mod test; -// pub use test::expect::ExpectedCallTracker; -pub use foundry_cheatcodes_common::expect::ExpectedCallTracker; mod toml; @@ -66,63 +66,39 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { self.apply(ccx.state) } - #[inline] - fn apply_traced(&self, ccx: &mut CheatsCtxt) -> Result { - let _span = trace_span_and_call(self); - let result = self.apply_full(ccx); - trace_return(&result); - return result; - - // Separate and non-generic functions to avoid inline and monomorphization bloat. - #[inline(never)] - fn trace_span_and_call(cheat: &dyn DynCheatcode) -> tracing::span::EnteredSpan { - let span = debug_span!(target: "cheatcodes", "apply"); - if !span.is_disabled() { - if enabled!(tracing::Level::TRACE) { - span.record("cheat", tracing::field::debug(cheat.as_debug())); - } else { - span.record("id", cheat.cheatcode().func.id); - } - } - let entered = span.entered(); - trace!(target: "cheatcodes", "applying"); - entered - } - - #[inline(never)] - fn trace_return(result: &Result) { - trace!( - target: "cheatcodes", - return = match result { - Ok(b) => hex::encode(b), - Err(e) => e.to_string(), - } - ); - } + /// Applies this cheatcode to the given context and executor. + /// + /// Implement this function if you need access to the executor. + #[inline(always)] + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + _executor: &mut E, + ) -> Result { + self.apply_stateful(ccx) } } pub(crate) trait DynCheatcode { - fn cheatcode(&self) -> &'static foundry_cheatcodes_spec::Cheatcode<'static>; + fn id(&self) -> &'static str; fn as_debug(&self) -> &dyn std::fmt::Debug; } impl DynCheatcode for T { - fn cheatcode(&self) -> &'static foundry_cheatcodes_spec::Cheatcode<'static> { - T::CHEATCODE + fn id(&self) -> &'static str { + T::CHEATCODE.func.id } - fn as_debug(&self) -> &dyn std::fmt::Debug { self } } -/// The cheatcode context, used in [`Cheatcode`]. -pub(crate) struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { +/// The cheatcode context, used in `Cheatcode`. +pub struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { /// The cheatcodes inspector state. pub(crate) state: &'cheats mut Cheatcodes, /// The EVM data. @@ -131,6 +107,8 @@ pub(crate) struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { pub(crate) precompiles: &'evm mut ContextPrecompiles, /// The original `msg.sender`. pub(crate) caller: Address, + /// Gas limit of the current cheatcode call. + pub(crate) gas_limit: u64, } impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'evm, DB> { diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 1f84e2475..af4457f8e 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -8,49 +8,49 @@ use parking_lot::Mutex; use std::sync::Arc; impl Cheatcode for broadcast_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, true) } } impl Cheatcode for broadcast_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } impl Cheatcode for broadcast_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } impl Cheatcode for startBroadcast_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, false) } } impl Cheatcode for startBroadcast_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } impl Cheatcode for startBroadcast_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } impl Cheatcode for stopBroadcastCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index 07e9e89f5..7b7d9b505 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -2,7 +2,7 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::U256; +use alloy_primitives::{hex, U256}; use alloy_sol_types::SolValue; // address @@ -166,7 +166,7 @@ where } #[instrument(target = "cheatcodes", level = "debug", skip(ty), fields(%ty), ret)] -fn parse_value(s: &str, ty: &DynSolType) -> Result { +pub(super) fn parse_value(s: &str, ty: &DynSolType) -> Result { match ty.coerce_str(s) { Ok(value) => Ok(value), Err(e) => match parse_value_fallback(s, ty) { diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 7ab8c2232..015e1e917 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -10,7 +10,7 @@ pub(crate) mod assert; pub(crate) mod expect; impl Cheatcode for zkVmCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { enable } = *self; if enable { @@ -24,7 +24,7 @@ impl Cheatcode for zkVmCall { } impl Cheatcode for zkRegisterContractCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, evmBytecodeHash, @@ -71,14 +71,14 @@ impl Cheatcode for assumeCall { } impl Cheatcode for breakpoint_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } @@ -115,7 +115,7 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skipCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { skipTest } = *self; if skipTest { // Skip should not work if called deeper than at test level. diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 9f1eee252..4ab97c031 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -1,10 +1,12 @@ -use std::fmt::{Debug, Display}; - -use alloy_primitives::{I256, U256}; -use foundry_common::console::{format_units_int, format_units_uint}; +use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::{hex, I256, U256}; +use foundry_evm_core::{ + abi::{format_units_int, format_units_uint}, + backend::{DatabaseExt, GLOBAL_FAIL_SLOT}, + constants::CHEATCODE_ADDRESS, +}; use itertools::Itertools; - -use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use std::fmt::{Debug, Display}; const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); @@ -167,871 +169,282 @@ impl EqRelAssertionError { type ComparisonResult<'a, T> = Result, ComparisonAssertionError<'a, T>>; -impl Cheatcode for assertTrue_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_true(self.condition).map_err(|e| e.to_string())?) - } -} - -impl Cheatcode for assertTrue_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_true(self.condition).map_err(|_| self.error.to_string())?) - } -} - -impl Cheatcode for assertFalse_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_false(self.condition).map_err(|e| e.to_string())?) - } -} - -impl Cheatcode for assertFalse_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_false(self.condition).map_err(|_| self.error.to_string())?) - } -} - -impl Cheatcode for assertEq_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_4Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_5Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_6Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_7Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_8Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_9Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_10Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_11Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_12Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_13Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertEq_14Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_15Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_16Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_17Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_18Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_19Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_20Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_21Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_22Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_23Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_24Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_25Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_26Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - let left = left.iter().map(hex::encode_prefixed).collect::>(); - let right = right.iter().map(hex::encode_prefixed).collect::>(); - Ok(assert_eq(&left, &right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEq_27Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - let left = left.iter().map(hex::encode_prefixed).collect::>(); - let right = right.iter().map(hex::encode_prefixed).collect::>(); - Ok(assert_eq(&left, &right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertEqDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_eq(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertEqDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_eq(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertEqDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_eq(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertEqDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_eq(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertNotEq_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_4Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_5Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_6Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_7Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_8Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_9Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_10Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_11Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_12Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_13Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) - .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertNotEq_14Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_15Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_16Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_17Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_18Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_19Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_20Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_21Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_22Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_23Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_24Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_25Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_not_eq(left, right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_26Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - let left = left.iter().map(hex::encode_prefixed).collect::>(); - let right = right.iter().map(hex::encode_prefixed).collect::>(); - Ok(assert_not_eq(&left, &right) - .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEq_27Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - let left = left.iter().map(hex::encode_prefixed).collect::>(); - let right = right.iter().map(hex::encode_prefixed).collect::>(); - Ok(assert_not_eq(&left, &right) - .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) - } -} - -impl Cheatcode for assertNotEqDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_not_eq(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertNotEqDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_not_eq(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertNotEqDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_not_eq(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertNotEqDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_not_eq(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGt_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_gt(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertGt_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_gt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertGt_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_gt(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertGt_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_gt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertGtDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_gt(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGtDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_gt(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGtDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_gt(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGtDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_gt(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGe_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_ge(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertGe_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_ge(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertGe_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_ge(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertGe_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_ge(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } -} - -impl Cheatcode for assertGeDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_ge(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGeDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_ge(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGeDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_ge(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertGeDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_ge(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } -} - -impl Cheatcode for assertLt_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_lt(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} - -impl Cheatcode for assertLt_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_lt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) +fn handle_assertion_result( + result: core::result::Result, ERR>, + ccx: &mut CheatsCtxt, + executor: &mut E, + error_formatter: impl Fn(&ERR) -> String, + error_msg: Option<&str>, + format_error: bool, +) -> Result { + match result { + Ok(_) => Ok(Default::default()), + Err(err) => { + let error_msg = error_msg.unwrap_or("assertion failed").to_string(); + let msg = if format_error { + format!("{error_msg}: {}", error_formatter(&err)) + } else { + error_msg + }; + if ccx.state.config.assertions_revert { + Err(msg.into()) + } else { + executor.console_log(ccx, msg); + ccx.ecx.sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; + Ok(Default::default()) + } + } } } -impl Cheatcode for assertLt_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_lt(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } -} +/// Implements [crate::Cheatcode] for pairs of cheatcodes. +/// +/// Accepts a list of pairs of cheatcodes, where the first cheatcode is the one that doesn't contain +/// a custom error message, and the second one contains it at `error` field. +/// +/// Passed `args` are the common arguments for both cheatcode structs (excluding `error` field). +/// +/// Macro also accepts an optional closure that formats the error returned by the assertion. +macro_rules! impl_assertions { + (|$($arg:ident),*| $body:expr, $format_error:literal, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + impl_assertions!(@args_tt |($($arg),*)| $body, |e| e.to_string(), $format_error, $(($no_error, $with_error),)*); + }; + (|$($arg:ident),*| $body:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + impl_assertions!(@args_tt |($($arg),*)| $body, |e| e.to_string(), true, $(($no_error, $with_error),)*); + }; + (|$($arg:ident),*| $body:expr, $error_formatter:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + impl_assertions!(@args_tt |($($arg),*)| $body, $error_formatter, true, $(($no_error, $with_error)),*); + }; + // We convert args to `tt` and later expand them back into tuple to allow usage of expanded args inside of + // each assertion type context. + (@args_tt |$args:tt| $body:expr, $error_formatter:expr, $format_error:literal, $(($no_error:ident, $with_error:ident)),* $(,)?) => { + $( + impl_assertions!(@impl $no_error, $with_error, $args, $body, $error_formatter, $format_error); + )* + }; + (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr, $format_error:literal) => { + impl crate::Cheatcode for $no_error { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { $($arg),* } = self; + handle_assertion_result($body, ccx, executor, $error_formatter, None, $format_error) + } + } -impl Cheatcode for assertLt_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_lt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } + impl crate::Cheatcode for $with_error { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { $($arg),*, error} = self; + handle_assertion_result($body, ccx, executor, $error_formatter, Some(error), $format_error) + } + } + }; } -impl Cheatcode for assertLtDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_lt(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |condition| assert_true(*condition), + false, + (assertTrue_0Call, assertTrue_1Call), } -impl Cheatcode for assertLtDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_lt(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |condition| assert_false(*condition), + false, + (assertFalse_0Call, assertFalse_1Call), } -impl Cheatcode for assertLtDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_lt(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_eq(left, right), + |e| e.format_for_values(), + (assertEq_0Call, assertEq_1Call), + (assertEq_2Call, assertEq_3Call), + (assertEq_4Call, assertEq_5Call), + (assertEq_6Call, assertEq_7Call), + (assertEq_8Call, assertEq_9Call), + (assertEq_10Call, assertEq_11Call), } -impl Cheatcode for assertLtDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_lt(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), + |e| e.format_for_values(), + (assertEq_12Call, assertEq_13Call), } -impl Cheatcode for assertLe_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_le(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } +impl_assertions! { + |left, right| assert_eq(left, right), + |e| e.format_for_arrays(), + (assertEq_14Call, assertEq_15Call), + (assertEq_16Call, assertEq_17Call), + (assertEq_18Call, assertEq_19Call), + (assertEq_20Call, assertEq_21Call), + (assertEq_22Call, assertEq_23Call), + (assertEq_24Call, assertEq_25Call), } -impl Cheatcode for assertLe_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_le(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } +impl_assertions! { + |left, right| assert_eq( + &left.iter().map(hex::encode_prefixed).collect::>(), + &right.iter().map(hex::encode_prefixed).collect::>(), + ), + |e| e.format_for_arrays(), + (assertEq_26Call, assertEq_27Call), } -impl Cheatcode for assertLe_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right } = self; - Ok(assert_le(left, right) - .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) - } +impl_assertions! { + |left, right, decimals| assert_eq(left, right), + |e| e.format_with_decimals(decimals), + (assertEqDecimal_0Call, assertEqDecimal_1Call), + (assertEqDecimal_2Call, assertEqDecimal_3Call), } -impl Cheatcode for assertLe_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { left, right, error } = self; - Ok(assert_le(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) - } +impl_assertions! { + |left, right| assert_not_eq(left, right), + |e| e.format_for_values(), + (assertNotEq_0Call, assertNotEq_1Call), + (assertNotEq_2Call, assertNotEq_3Call), + (assertNotEq_4Call, assertNotEq_5Call), + (assertNotEq_6Call, assertNotEq_7Call), + (assertNotEq_8Call, assertNotEq_9Call), + (assertNotEq_10Call, assertNotEq_11Call), } -impl Cheatcode for assertLeDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_le(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), + |e| e.format_for_values(), + (assertNotEq_12Call, assertNotEq_13Call), } -impl Cheatcode for assertLeDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_le(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_not_eq(left, right), + |e| e.format_for_arrays(), + (assertNotEq_14Call, assertNotEq_15Call), + (assertNotEq_16Call, assertNotEq_17Call), + (assertNotEq_18Call, assertNotEq_19Call), + (assertNotEq_20Call, assertNotEq_21Call), + (assertNotEq_22Call, assertNotEq_23Call), + (assertNotEq_24Call, assertNotEq_25Call), } -impl Cheatcode for assertLeDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_le(&self.left, &self.right) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_not_eq( + &left.iter().map(hex::encode_prefixed).collect::>(), + &right.iter().map(hex::encode_prefixed).collect::>(), + ), + |e| e.format_for_arrays(), + (assertNotEq_26Call, assertNotEq_27Call), } -impl Cheatcode for assertLeDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(assert_le(&self.left, &self.right) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, decimals| assert_not_eq(left, right), + |e| e.format_with_decimals(decimals), + (assertNotEqDecimal_0Call, assertNotEqDecimal_1Call), + (assertNotEqDecimal_2Call, assertNotEqDecimal_3Call), } -impl Cheatcode for assertApproxEqAbs_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {e}"))?) - } +impl_assertions! { + |left, right| assert_gt(left, right), + |e| e.format_for_values(), + (assertGt_0Call, assertGt_1Call), + (assertGt_2Call, assertGt_3Call), } -impl Cheatcode for assertApproxEqAbs_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("{}: {}", self.error, e))?) - } +impl_assertions! { + |left, right, decimals| assert_gt(left, right), + |e| e.format_with_decimals(decimals), + (assertGtDecimal_0Call, assertGtDecimal_1Call), + (assertGtDecimal_2Call, assertGtDecimal_3Call), } -impl Cheatcode for assertApproxEqAbs_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {e}"))?) - } +impl_assertions! { + |left, right| assert_ge(left, right), + |e| e.format_for_values(), + (assertGe_0Call, assertGe_1Call), + (assertGe_2Call, assertGe_3Call), } -impl Cheatcode for assertApproxEqAbs_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("{}: {}", self.error, e))?) - } +impl_assertions! { + |left, right, decimals| assert_ge(left, right), + |e| e.format_with_decimals(decimals), + (assertGeDecimal_0Call, assertGeDecimal_1Call), + (assertGeDecimal_2Call, assertGeDecimal_3Call), } -impl Cheatcode for assertApproxEqAbsDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_lt(left, right), + |e| e.format_for_values(), + (assertLt_0Call, assertLt_1Call), + (assertLt_2Call, assertLt_3Call), } -impl Cheatcode for assertApproxEqAbsDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, decimals| assert_lt(left, right), + |e| e.format_with_decimals(decimals), + (assertLtDecimal_0Call, assertLtDecimal_1Call), + (assertLtDecimal_2Call, assertLtDecimal_3Call), } -impl Cheatcode for assertApproxEqAbsDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right| assert_le(left, right), + |e| e.format_for_values(), + (assertLe_0Call, assertLe_1Call), + (assertLe_2Call, assertLe_3Call), } -impl Cheatcode for assertApproxEqAbsDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, decimals| assert_le(left, right), + |e| e.format_with_decimals(decimals), + (assertLeDecimal_0Call, assertLeDecimal_1Call), + (assertLeDecimal_2Call, assertLeDecimal_3Call), } -impl Cheatcode for assertApproxEqRel_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {e}"))?) - } +impl_assertions! { + |left, right, maxDelta| uint_assert_approx_eq_abs(*left, *right, *maxDelta), + (assertApproxEqAbs_0Call, assertApproxEqAbs_1Call), } -impl Cheatcode for assertApproxEqRel_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("{}: {}", self.error, e))?) - } +impl_assertions! { + |left, right, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), + (assertApproxEqAbs_2Call, assertApproxEqAbs_3Call), } -impl Cheatcode for assertApproxEqRel_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {e}"))?) - } +impl_assertions! { + |left, right, decimals, maxDelta| uint_assert_approx_eq_abs(*left, *right, *maxDelta), + |e| e.format_with_decimals(decimals), + (assertApproxEqAbsDecimal_0Call, assertApproxEqAbsDecimal_1Call), } -impl Cheatcode for assertApproxEqRel_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("{}: {}", self.error, e))?) - } +impl_assertions! { + |left, right, decimals, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), + |e| e.format_with_decimals(decimals), + (assertApproxEqAbsDecimal_2Call, assertApproxEqAbsDecimal_3Call), } -impl Cheatcode for assertApproxEqRelDecimal_0Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, maxPercentDelta| uint_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + (assertApproxEqRel_0Call, assertApproxEqRel_1Call), } -impl Cheatcode for assertApproxEqRelDecimal_1Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, maxPercentDelta| int_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + (assertApproxEqRel_2Call, assertApproxEqRel_3Call), } -impl Cheatcode for assertApproxEqRelDecimal_2Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, decimals, maxPercentDelta| uint_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + |e| e.format_with_decimals(decimals), + (assertApproxEqRelDecimal_0Call, assertApproxEqRelDecimal_1Call), } -impl Cheatcode for assertApproxEqRelDecimal_3Call { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) - } +impl_assertions! { + |left, right, decimals, maxPercentDelta| int_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + |e| e.format_with_decimals(decimals), + (assertApproxEqRelDecimal_2Call, assertApproxEqRelDecimal_3Call), } fn assert_true(condition: bool) -> Result, SimpleAssertionError> { diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 8fe5ef4d4..3fc6a32d6 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -1,5 +1,5 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{address, Address, Bytes, LogData as RawLog, U256}; +use alloy_primitives::{address, hex, Address, Bytes, LogData as RawLog, U256}; use alloy_sol_types::{SolError, SolValue}; use foundry_cheatcodes_common::expect::{ExpectedCallData, ExpectedCallType}; use revm::interpreter::{return_ok, InstructionResult}; @@ -48,13 +48,16 @@ pub struct ExpectedEmit { pub log: Option, /// The checks to perform: /// ```text - /// ┌───────┬───────┬───────┬────┐ - /// │topic 1│topic 2│topic 3│data│ - /// └───────┴───────┴───────┴────┘ + /// ┌───────┬───────┬───────┬───────┬────┐ + /// │topic 0│topic 1│topic 2│topic 3│data│ + /// └───────┴───────┴───────┴───────┴────┘ /// ``` - pub checks: [bool; 4], + pub checks: [bool; 5], /// If present, check originating address against this pub address: Option
, + /// If present, relax the requirement that topic 0 must be present. This allows anonymous + /// events with no indexed topics to be matched. + pub anonymous: bool, /// Whether the log was actually found in the subcalls pub found: bool, } @@ -161,93 +164,135 @@ impl Cheatcode for expectCallMinGas_1Call { } impl Cheatcode for expectEmit_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, ccx.ecx.journaled_state.depth(), - [checkTopic1, checkTopic2, checkTopic3, checkData], + [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, + false, ) } } impl Cheatcode for expectEmit_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, ccx.ecx.journaled_state.depth(), - [checkTopic1, checkTopic2, checkTopic3, checkData], + [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), + false, ) } } impl Cheatcode for expectEmit_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], None) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false) } } impl Cheatcode for expectEmit_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], Some(emitter)) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), false) + } +} + +impl Cheatcode for expectEmitAnonymous_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], + None, + true, + ) + } +} + +impl Cheatcode for expectEmitAnonymous_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], + Some(emitter), + true, + ) + } +} + +impl Cheatcode for expectEmitAnonymous_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, true) + } +} + +impl Cheatcode for expectEmitAnonymous_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { emitter } = *self; + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), true) } } impl Cheatcode for expectRevert_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for _expectCheatcodeRevert_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for expectSafeMemoryCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) } } impl Cheatcode for stopExpectSafeMemoryCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); Ok(Default::default()) @@ -255,7 +300,7 @@ impl Cheatcode for stopExpectSafeMemoryCall { } impl Cheatcode for expectSafeMemoryCallCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) } @@ -348,8 +393,9 @@ fn expect_call( fn expect_emit( state: &mut Cheatcodes, depth: u64, - checks: [bool; 4], + checks: [bool; 5], address: Option
, + anonymous: bool, ) -> Result { state.expected_emits.push_back(ExpectedEmit { depth, @@ -357,6 +403,7 @@ fn expect_emit( address, found: false, log: None, + anonymous, }); Ok(Default::default()) } @@ -376,7 +423,7 @@ pub(crate) fn handle_expect_emit(state: &mut Cheatcodes, log: &alloy_primitives: return } - // if there's anything to fill, we need to pop back. + // If there's anything to fill, we need to pop back. // Otherwise, if there are any events that are unmatched, we try to match to match them // in the order declared, so we start popping from the front (like a queue). let mut event_to_fill_or_check = @@ -388,38 +435,42 @@ pub(crate) fn handle_expect_emit(state: &mut Cheatcodes, log: &alloy_primitives: .expect("we should have an emit to fill or check"); let Some(expected) = &event_to_fill_or_check.log else { - // Fill the event. - event_to_fill_or_check.log = Some(log.data.clone()); + // Unless the caller is trying to match an anonymous event, the first topic must be + // filled. + // TODO: failing this check should probably cause a warning + if event_to_fill_or_check.anonymous || log.topics().first().is_some() { + event_to_fill_or_check.log = Some(log.data.clone()); + } state.expected_emits.push_back(event_to_fill_or_check); return }; - let expected_topic_0 = expected.topics().first(); - let log_topic_0 = log.topics().first(); - - if expected_topic_0 - .zip(log_topic_0) - .map_or(false, |(a, b)| a == b && expected.topics().len() == log.topics().len()) - { - // Match topics - event_to_fill_or_check.found = log + event_to_fill_or_check.found = || -> bool { + // Topic count must match. + if expected.topics().len() != log.topics().len() { + return false + } + // Match topics according to the checks. + if !log .topics() .iter() - .skip(1) .enumerate() .filter(|(i, _)| event_to_fill_or_check.checks[*i]) - .all(|(i, topic)| topic == &expected.topics()[i + 1]); - - // Maybe match source address - if let Some(addr) = event_to_fill_or_check.address { - event_to_fill_or_check.found &= addr == log.address; + .all(|(i, topic)| topic == &expected.topics()[i]) + { + return false } - - // Maybe match data - if event_to_fill_or_check.checks[3] { - event_to_fill_or_check.found &= expected.data.as_ref() == log.data.data.as_ref(); + // Maybe match source address. + if event_to_fill_or_check.address.map_or(false, |addr| addr != log.address) { + return false; } - } + // Maybe match data. + if event_to_fill_or_check.checks[4] && expected.data.as_ref() != log.data.data.as_ref() { + return false + } + + true + }(); // If we found the event, we can push it to the back of the queue // and begin expecting the next event. diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs index e1827dbef..e83a18390 100644 --- a/crates/cheatcodes/src/toml.rs +++ b/crates/cheatcodes/src/toml.rs @@ -45,7 +45,7 @@ impl Cheatcode for parseTomlUintCall { impl Cheatcode for parseTomlUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::Uint(256)) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } @@ -59,7 +59,7 @@ impl Cheatcode for parseTomlIntCall { impl Cheatcode for parseTomlIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::Int(256)) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } @@ -73,7 +73,7 @@ impl Cheatcode for parseTomlBoolCall { impl Cheatcode for parseTomlBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::Bool) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } @@ -87,7 +87,7 @@ impl Cheatcode for parseTomlAddressCall { impl Cheatcode for parseTomlAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::Address) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } @@ -101,7 +101,7 @@ impl Cheatcode for parseTomlStringCall { impl Cheatcode for parseTomlStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::String) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String))) } } @@ -115,7 +115,7 @@ impl Cheatcode for parseTomlBytesCall { impl Cheatcode for parseTomlBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::Bytes) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } @@ -129,7 +129,7 @@ impl Cheatcode for parseTomlBytes32Call { impl Cheatcode for parseTomlBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; - parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 8d4c547df..08e255341 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -46,14 +46,14 @@ impl Cheatcode for createWallet_2Call { } impl Cheatcode for getNonce_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { wallet } = self; super::evm::get_nonce(ccx, &wallet.addr) } } impl Cheatcode for sign_3Call { - fn apply_full(&self, _: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, _: &mut CheatsCtxt) -> Result { let Self { wallet, digest } = self; sign(&wallet.privateKey, digest) } @@ -88,7 +88,7 @@ impl Cheatcode for deriveKey_3Call { } impl Cheatcode for rememberKeyCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; let wallet = parse_wallet(privateKey)?; let address = wallet.address(); @@ -158,11 +158,17 @@ impl Cheatcode for randomUint_0Call { impl Cheatcode for randomUint_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { min, max } = self; + let Self { min, max } = *self; + ensure!(min <= max, "min must be less than or equal to max"); // Generate random between range min..=max let mut rng = rand::thread_rng(); - let range = *max - *min + U256::from(1); - let random_number = rng.gen::() % range + *min; + let exclusive_modulo = max - min; + let mut random_number = rng.gen::(); + if exclusive_modulo != U256::MAX { + let inclusive_modulo = exclusive_modulo + U256::from(1); + random_number %= inclusive_modulo; + } + random_number += min; Ok(random_number.abi_encode()) } } @@ -310,8 +316,7 @@ fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { mod tests { use super::*; use crate::CheatsConfig; - use alloy_primitives::FixedBytes; - use hex::FromHex; + use alloy_primitives::{hex::FromHex, FixedBytes}; use p256::ecdsa::signature::hazmat::PrehashVerifier; use std::{path::PathBuf, sync::Arc}; diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 5c13c8e01..909b7525c 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -17,6 +17,7 @@ use foundry_config::{Config, RpcEndpoint}; use foundry_evm::{ decode::decode_console_logs, traces::{ + decode_trace_arena, identifier::{SignaturesIdentifier, TraceIdentifiers}, render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, }, @@ -932,10 +933,11 @@ impl ChiselDispatcher { } println!("{}", "Traces:".green()); - for (kind, trace) in &result.traces { + for (kind, trace) in &mut result.traces { // Display all Setup + Execution traces. if matches!(kind, TraceKind::Setup | TraceKind::Execution) { - println!("{}", render_trace_arena(trace, decoder).await?); + decode_trace_arena(trace, decoder).await?; + println!("{}", render_trace_arena(trace)); } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index b2033af96..05f4db1a6 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -13,7 +13,7 @@ use eyre::{Result, WrapErr}; use foundry_compilers::Artifact; use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, - inspectors::CheatsConfig, + inspectors::CheatsConfig, traces::TraceMode, }; use solang_parser::pt::{self, CodeLocation}; use std::str::FromStr; @@ -302,7 +302,7 @@ impl SessionSource { // Build a new executor let executor = ExecutorBuilder::new() .inspectors(|stack| { - stack.chisel_state(final_pc).trace(true).cheatcodes( + stack.chisel_state(final_pc).trace_mode(TraceMode::Call).cheatcodes( CheatsConfig::new( &self.config.foundry_config, self.config.evm_opts.clone(), @@ -317,6 +317,7 @@ impl SessionSource { }) .gas_limit(self.config.evm_opts.gas_limit()) .spec(self.config.foundry_config.evm_spec_id()) + .legacy_assertions(self.config.foundry_config.legacy_assertions) .build(env, backend); // Create a [ChiselRunner] with a default balance of [U256::MAX] and @@ -419,8 +420,7 @@ fn format_token(token: DynSolValue) -> String { DynSolValue::Tuple(tokens) => { let displayed_types = tokens .iter() - .map(|t| t.sol_type_name().to_owned()) - .map(|t| t.unwrap_or_default().into_owned()) + .map(|t| t.sol_type_name().unwrap_or_default()) .collect::>() .join(", "); let mut out = diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index ff4c9695e..e78454ee3 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -7,7 +7,7 @@ use alloy_primitives::{Address, Bytes, Log, U256}; use eyre::Result; use foundry_evm::{ executors::{DeployResult, Executor, RawCallResult}, - traces::{CallTraceArena, TraceKind}, + traces::{TraceKind, Traces}, }; use revm::interpreter::{return_ok, InstructionResult}; use std::collections::HashMap; @@ -39,7 +39,7 @@ pub struct ChiselResult { /// Transaction logs pub logs: Vec, /// Call traces - pub traces: Vec<(TraceKind, CallTraceArena)>, + pub traces: Traces, /// Amount of gas used in the transaction pub gas_used: u64, /// Map of addresses to their labels @@ -125,19 +125,20 @@ impl ChiselRunner { value: U256, commit: bool, ) -> eyre::Result { - let fs_commit_changed = if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { - let original_fs_commit = cheatcodes.fs_commit; - cheatcodes.fs_commit = false; - original_fs_commit != cheatcodes.fs_commit - } else { - false - }; + let fs_commit_changed = + if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes { + let original_fs_commit = cheatcodes.fs_commit; + cheatcodes.fs_commit = false; + original_fs_commit != cheatcodes.fs_commit + } else { + false + }; let mut res = self.executor.call_raw(from, to, calldata.clone(), value)?; let mut gas_used = res.gas_used; if matches!(res.exit_reason, return_ok!()) { // store the current gas limit and reset it later - let init_gas_limit = self.executor.env.tx.gas_limit; + let init_gas_limit = self.executor.env().tx.gas_limit; // the executor will return the _exact_ gas value this transaction consumed, setting // this value as gas limit will result in `OutOfGas` so to come up with a @@ -148,7 +149,7 @@ impl ChiselRunner { let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env.tx.gas_limit = mid_gas_limit; + self.executor.env_mut().tx.gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.clone(), value)?; match res.exit_reason { InstructionResult::Revert | @@ -174,13 +175,13 @@ impl ChiselRunner { } } // reset gas limit in the - self.executor.env.tx.gas_limit = init_gas_limit; + self.executor.env_mut().tx.gas_limit = init_gas_limit; } // if we changed `fs_commit` during gas limit search, re-execute the call with original // value if fs_commit_changed { - if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { + if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes { cheatcodes.fs_commit = !cheatcodes.fs_commit; } diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index f83eefeae..5ba75238a 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -520,7 +520,7 @@ contract {contract_name} {{ pt::Import::Rename(s, _, _) | pt::Import::GlobalSymbol(s, _, _) => { let s = match s { - pt::ImportPath::Filename(s) => s.string.clone(), + pt::ImportPath::Filename(s) => s.string, pt::ImportPath::Path(p) => p.to_string(), }; let path = PathBuf::from(s); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index f13dbe5f6..9c8b3fab8 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -33,19 +33,19 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } color-eyre.workspace = true dotenvy = "0.15" eyre.workspace = true +futures.workspace = true indicatif = "0.17" once_cell.workspace = true regex = { version = "1", default-features = false } serde.workspace = true -strsim = "0.10" +strsim = "0.11" strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } -tracing-error = "0.2" -tracing-subscriber = { workspace = true, features = ["registry", "env-filter", "fmt"] } +tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } tracing.workspace = true yansi.workspace = true -hex.workspace = true -futures.workspace = true + +tracing-tracy = { version = "0.11", optional = true } [dev-dependencies] tempfile.workspace = true @@ -54,3 +54,5 @@ tempfile.workspace = true default = ["rustls"] rustls = ["foundry-wallets/rustls"] openssl = ["foundry-compilers/openssl"] + +tracy = ["dep:tracing-tracy"] diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index d6e34f3b8..4f69c2ca4 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -48,13 +48,11 @@ impl EyreHandler for Handler { /// /// Panics are always caught by the more debug-centric handler. pub fn install() { - // If the user has not explicitly overridden "RUST_BACKTRACE", then produce full backtraces. if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "full"); + std::env::set_var("RUST_BACKTRACE", "1"); } - let debug_enabled = std::env::var("FOUNDRY_DEBUG").is_ok(); - if debug_enabled { + if std::env::var_os("FOUNDRY_DEBUG").is_some() { if let Err(e) = color_eyre::install() { debug!("failed to install color eyre error hook: {e}"); } diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs index a52bdc158..e903804de 100644 --- a/crates/cli/src/utils/abi.rs +++ b/crates/cli/src/utils/abi.rs @@ -1,6 +1,6 @@ use alloy_chains::Chain; use alloy_json_abi::Function; -use alloy_primitives::Address; +use alloy_primitives::{hex, Address}; use alloy_provider::{network::AnyNetwork, Provider}; use alloy_transport::Transport; use eyre::{OptionExt, Result}; diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 29377213b..2dc620de3 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -12,10 +12,11 @@ use foundry_compilers::{ use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; use foundry_debugger::Debugger; use foundry_evm::{ - debug::DebugArena, executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ + debug::DebugTraceIdentifier, + decode_trace_arena, identifier::{EtherscanIdentifier, SignaturesIdentifier}, render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, @@ -35,7 +36,7 @@ pub fn remove_contract( path: &Path, name: &str, ) -> Result<(JsonAbi, CompactBytecode, CompactDeployedBytecode)> { - let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) { + let contract = if let Some(contract) = output.remove(path, name) { contract } else { let mut err = format!("could not find artifact: `{name}`"); @@ -79,7 +80,7 @@ pub fn remove_zk_contract( path: &Path, name: &str, ) -> Result { - let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) { + let contract = if let Some(contract) = output.remove(path, name) { contract } else { let mut err = format!("could not find artifact: `{name}`"); @@ -347,20 +348,14 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result, - pub debug: Option, pub gas_used: u64, } impl TraceResult { /// Create a new [`TraceResult`] from a [`RawCallResult`]. pub fn from_raw(raw: RawCallResult, trace_kind: TraceKind) -> Self { - let RawCallResult { gas_used, traces, reverted, debug, .. } = raw; - Self { - success: !reverted, - traces: traces.map(|arena| vec![(trace_kind, arena)]), - debug, - gas_used, - } + let RawCallResult { gas_used, traces, reverted, .. } = raw; + Self { success: !reverted, traces: traces.map(|arena| vec![(trace_kind, arena)]), gas_used } } } @@ -389,6 +384,7 @@ pub async fn handle_traces( chain: Option, labels: Vec, debug: bool, + decode_internal: bool, ) -> Result<()> { let labels = labels.iter().filter_map(|label_str| { let mut iter = label_str.split(':'); @@ -416,6 +412,15 @@ pub async fn handle_traces( } } + if decode_internal { + let sources = if let Some(etherscan_identifier) = ðerscan_identifier { + etherscan_identifier.get_compiled_contracts().await? + } else { + Default::default() + }; + decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); + } + if debug { let sources = if let Some(etherscan_identifier) = etherscan_identifier { etherscan_identifier.get_compiled_contracts().await? @@ -423,7 +428,7 @@ pub async fn handle_traces( Default::default() }; let mut debugger = Debugger::builder() - .debug_arena(result.debug.as_ref().expect("missing debug arena")) + .traces(result.traces.expect("missing traces")) .decoder(&decoder) .sources(sources) .build(); @@ -436,11 +441,12 @@ pub async fn handle_traces( } pub async fn print_traces(result: &mut TraceResult, decoder: &CallTraceDecoder) -> Result<()> { - let traces = result.traces.as_ref().expect("No traces found"); + let traces = result.traces.as_mut().expect("No traces found"); println!("Traces:"); for (_, arena) in traces { - println!("{}", render_trace_arena(arena, decoder).await?); + decode_trace_arena(arena, decoder).await?; + println!("{}", render_trace_arena(arena)); } println!(); diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 3a3e65c5e..15864b016 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -12,7 +12,6 @@ use std::{ process::{Command, Output, Stdio}, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; mod cmd; @@ -67,13 +66,12 @@ impl> FoundryPathExt for T { } /// Initializes a tracing Subscriber for logging -#[allow(dead_code)] pub fn subscriber() { - tracing_subscriber::Registry::default() - .with(tracing_subscriber::EnvFilter::from_default_env()) - .with(ErrorLayer::default()) - .with(tracing_subscriber::fmt::layer()) - .init() + let registry = tracing_subscriber::Registry::default() + .with(tracing_subscriber::EnvFilter::from_default_env()); + #[cfg(feature = "tracy")] + let registry = registry.with(tracing_tracy::TracyLayer::default()); + registry.with(tracing_subscriber::fmt::layer()).init() } pub fn abi_to_solidity(abi: &JsonAbi, name: &str) -> Result { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a9d421467..b80e184a3 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,12 +17,11 @@ foundry-block-explorers = { workspace = true, features = [ "foundry-compilers", ] } foundry-zksync-compiler.workspace = true +foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true -foundry-macros.workspace = true foundry-linking.workspace = true -alloy-consensus.workspace = true alloy-contract.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true @@ -36,10 +35,9 @@ alloy-primitives = { workspace = true, features = [ alloy-provider.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true -alloy-rpc-types = { workspace = true, features = ["eth"] } -alloy-rpc-types-engine.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } alloy-serde.workspace = true -alloy-sol-types = { workspace = true, features = ["json"] } +alloy-sol-types.workspace = true alloy-transport-http = { workspace = true, features = [ "reqwest", "reqwest-rustls-tls", @@ -48,29 +46,17 @@ alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true alloy-transport.workspace = true -revm = { workspace = true, features = [ - "std", - "serde", - "memory_limit", - "optional_eip3607", - "optional_block_gas_limit", - "optional_no_base_fee", - "arbitrary", - "optimism", - "c-kzg", -] } - tower.workspace = true -derive_more.workspace = true -itertools.workspace = true async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -comfy-table = "7" +comfy-table.workspace = true dunce.workspace = true eyre.workspace = true +num-format.workspace = true once_cell.workspace = true reqwest.workspace = true +rustc-hash.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true @@ -80,14 +66,10 @@ tracing.workspace = true url.workspace = true walkdir.workspace = true yansi.workspace = true -rustc-hash.workspace = true -num-format.workspace = true -chrono.workspace = true # zksync globset = "0.4" [dev-dependencies] -foundry-macros.workspace = true similar-asserts.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml new file mode 100644 index 000000000..fabe35a7a --- /dev/null +++ b/crates/common/fmt/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "foundry-common-fmt" +description = "Common formatting utilities for Foundry" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-primitives.workspace = true +alloy-dyn-abi = { workspace = true, features = ["eip712"] } +yansi.workspace = true + +# ui +alloy-consensus.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth"] } +alloy-serde.workspace = true +serde.workspace = true +serde_json.workspace = true +chrono.workspace = true +revm-primitives.workspace = true +comfy-table.workspace = true + +[dev-dependencies] +foundry-macros.workspace = true +similar-asserts.workspace = true diff --git a/crates/common/src/fmt/console.rs b/crates/common/fmt/src/console.rs similarity index 100% rename from crates/common/src/fmt/console.rs rename to crates/common/fmt/src/console.rs diff --git a/crates/common/src/fmt/dynamic.rs b/crates/common/fmt/src/dynamic.rs similarity index 96% rename from crates/common/src/fmt/dynamic.rs rename to crates/common/fmt/src/dynamic.rs index 19330d474..5055e6561 100644 --- a/crates/common/src/fmt/dynamic.rs +++ b/crates/common/fmt/src/dynamic.rs @@ -1,7 +1,6 @@ use super::{format_int_exp, format_uint_exp}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::hex; -use eyre::Result; use std::fmt; /// [`DynSolValue`] formatter. @@ -111,13 +110,8 @@ impl fmt::Display for DynValueDisplay<'_> { /// Parses string input as Token against the expected ParamType pub fn parse_tokens<'a, I: IntoIterator>( params: I, -) -> Result> { - let mut tokens = Vec::new(); - for (param, value) in params { - let token = DynSolType::coerce_str(param, value)?; - tokens.push(token); - } - Ok(tokens) +) -> alloy_dyn_abi::Result> { + params.into_iter().map(|(param, value)| DynSolType::coerce_str(param, value)).collect() } /// Pretty-prints a slice of tokens using [`format_token`]. diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs new file mode 100644 index 000000000..639e175b4 --- /dev/null +++ b/crates/common/fmt/src/eof.rs @@ -0,0 +1,75 @@ +use comfy_table::{ContentArrangement, Table}; +use revm_primitives::{ + eof::{EofBody, EofHeader}, + Eof, +}; +use std::fmt::{self, Write}; + +pub fn pretty_eof(eof: &Eof) -> Result { + let Eof { + header: + EofHeader { + types_size, + code_sizes, + container_sizes, + data_size, + sum_code_sizes: _, + sum_container_sizes: _, + }, + body: + EofBody { types_section, code_section, container_section, data_section, is_data_filled: _ }, + raw: _, + } = eof; + + let mut result = String::new(); + + let mut table = Table::new(); + table.add_row(vec!["type_size", &types_size.to_string()]); + table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]); + if !code_sizes.is_empty() { + table.add_row(vec!["code_sizes", &format!("{code_sizes:?}")]); + } + table.add_row(vec!["num_container_sections", &container_sizes.len().to_string()]); + if !container_sizes.is_empty() { + table.add_row(vec!["container_sizes", &format!("{container_sizes:?}")]); + } + table.add_row(vec!["data_size", &data_size.to_string()]); + + write!(result, "Header:\n{table}")?; + + if !code_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + table.set_header(vec!["", "Inputs", "Outputs", "Max stack height", "Code"]); + for (idx, (code, type_section)) in code_section.iter().zip(types_section).enumerate() { + table.add_row(vec![ + &idx.to_string(), + &type_section.inputs.to_string(), + &type_section.outputs.to_string(), + &type_section.max_stack_size.to_string(), + &code.to_string(), + ]); + } + + write!(result, "\n\nCode sections:\n{table}")?; + } + + if !container_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + for (idx, container) in container_section.iter().enumerate() { + table.add_row(vec![&idx.to_string(), &container.to_string()]); + } + + write!(result, "\n\nContainer sections:\n{table}")?; + } + + if !data_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + table.add_row(vec![&data_section.to_string()]); + write!(result, "\n\nData section:\n{table}")?; + } + + Ok(result) +} diff --git a/crates/common/src/fmt/mod.rs b/crates/common/fmt/src/exp.rs similarity index 53% rename from crates/common/src/fmt/mod.rs rename to crates/common/fmt/src/exp.rs index a43fe7dea..84444615e 100644 --- a/crates/common/src/fmt/mod.rs +++ b/crates/common/fmt/src/exp.rs @@ -1,17 +1,40 @@ -//! Helpers for formatting Ethereum types. - -use crate::calc::to_exp_notation; use alloy_primitives::{Sign, I256, U256}; use yansi::Paint; -mod console; -pub use console::{console_format, ConsoleFmt, FormatSpec}; +/// Returns the number expressed as a string in exponential notation +/// with the given precision (number of significant figures), +/// optionally removing trailing zeros from the mantissa. +/// +/// Examples: +/// +/// ```text +/// precision = 4, trim_end_zeroes = false +/// 1234124124 -> 1.234e9 +/// 10000000 -> 1.000e7 +/// precision = 3, trim_end_zeroes = true +/// 1234124124 -> 1.23e9 +/// 10000000 -> 1e7 +/// ``` +#[inline] +pub fn to_exp_notation(value: U256, precision: usize, trim_end_zeros: bool, sign: Sign) -> String { + let stringified = value.to_string(); + let exponent = stringified.len() - 1; + let mut mantissa = stringified.chars().take(precision).collect::(); -mod dynamic; -pub use dynamic::{format_token, format_token_raw, format_tokens, parse_tokens}; + // optionally remove trailing zeros + if trim_end_zeros { + mantissa = mantissa.trim_end_matches('0').to_string(); + } -mod ui; -pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, get_pretty_tx_receipt_attr, UIfmt}; + // Place a decimal point only if needed + // e.g. 1234 -> 1.234e3 (needed) + // 5 -> 5 (not needed) + if mantissa.len() > 1 { + mantissa.insert(1, '.'); + } + + format!("{sign}{mantissa}e{exponent}") +} /// Formats a U256 number to string, adding an exponential notation _hint_ if it /// is larger than `10_000`, with a precision of `4` figures, and trimming the @@ -21,7 +44,7 @@ pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, get_pretty_tx_receipt_at /// /// ``` /// use alloy_primitives::U256; -/// use foundry_common::fmt::format_uint_exp as f; +/// use foundry_common_fmt::format_uint_exp as f; /// /// # yansi::disable(); /// assert_eq!(f(U256::from(0)), "0"); @@ -47,7 +70,7 @@ pub fn format_uint_exp(num: U256) -> String { /// /// ``` /// use alloy_primitives::I256; -/// use foundry_common::fmt::format_int_exp as f; +/// use foundry_common_fmt::format_int_exp as f; /// /// # yansi::disable(); /// assert_eq!(f(I256::try_from(0).unwrap()), "0"); @@ -74,3 +97,27 @@ pub fn format_int_exp(num: I256) -> String { let exp = to_exp_notation(abs, 4, true, sign); format!("{sign}{abs} {}", format!("[{exp}]").dim()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_to_exponential_notation() { + let value = 1234124124u64; + + let formatted = to_exp_notation(U256::from(value), 4, false, Sign::Positive); + assert_eq!(formatted, "1.234e9"); + + let formatted = to_exp_notation(U256::from(value), 3, true, Sign::Positive); + assert_eq!(formatted, "1.23e9"); + + let value = 10000000u64; + + let formatted = to_exp_notation(U256::from(value), 4, false, Sign::Positive); + assert_eq!(formatted, "1.000e7"); + + let formatted = to_exp_notation(U256::from(value), 3, true, Sign::Positive); + assert_eq!(formatted, "1e7"); + } +} diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs new file mode 100644 index 000000000..c02090809 --- /dev/null +++ b/crates/common/fmt/src/lib.rs @@ -0,0 +1,16 @@ +//! Helpers for formatting Ethereum types. + +mod console; +pub use console::{console_format, ConsoleFmt, FormatSpec}; + +mod dynamic; +pub use dynamic::{format_token, format_token_raw, format_tokens, parse_tokens}; + +mod exp; +pub use exp::{format_int_exp, format_uint_exp, to_exp_notation}; + +mod ui; +pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, EthValue, UIfmt}; + +mod eof; +pub use eof::pretty_eof; diff --git a/crates/common/src/fmt/ui.rs b/crates/common/fmt/src/ui.rs similarity index 92% rename from crates/common/src/fmt/ui.rs rename to crates/common/fmt/src/ui.rs index 0fe8dccd1..b9deffc7e 100644 --- a/crates/common/src/fmt/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1,8 +1,7 @@ //! Helper trait and functions to format Ethereum types. -use crate::TransactionReceiptWithRevertReason; -use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptWithBloom, TxType}; -use alloy_primitives::*; +use alloy_consensus::{AnyReceiptEnvelope, Eip658Value, Receipt, ReceiptWithBloom, TxType}; +use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, B256, I256, U256, U64}; use alloy_rpc_types::{ AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, TransactionReceipt, }; @@ -17,7 +16,7 @@ const NAME_COLUMN_LEN: usize = 20usize; /// # Examples /// /// ``` -/// use foundry_common::fmt::UIfmt; +/// use foundry_common_fmt::UIfmt; /// /// let boolean: bool = true; /// let string = boolean.pretty(); @@ -147,8 +146,13 @@ impl UIfmt for [u8] { } } -pub fn pretty_status(status: bool) -> String { - if status { "1 (success)" } else { "0 (failed)" }.to_string() +impl UIfmt for Eip658Value { + fn pretty(&self) -> String { + match self { + Self::Eip658(status) => if *status { "1 (success)" } else { "0 (failed)" }.to_string(), + Self::PostState(state) => state.pretty(), + } + } } impl UIfmt for AnyTransactionReceipt { @@ -177,6 +181,7 @@ impl UIfmt for AnyTransactionReceipt { }, blob_gas_price, blob_gas_used, + authorization_list, }, other, } = self; @@ -198,7 +203,8 @@ transactionHash {} transactionIndex {} type {} blobGasPrice {} -blobGasUsed {}", +blobGasUsed {} +authorizationList {}", block_hash.pretty(), block_number.pretty(), contract_address.pretty(), @@ -209,12 +215,16 @@ blobGasUsed {}", serde_json::to_string(&logs).unwrap(), logs_bloom.pretty(), state_root.pretty(), - pretty_status(status.coerce_status()), + status.pretty(), transaction_hash.pretty(), transaction_index.pretty(), transaction_type, blob_gas_price.pretty(), blob_gas_used.pretty(), + authorization_list + .as_ref() + .map(|l| serde_json::to_string(&l).unwrap()) + .unwrap_or_default(), ); if let Some(to) = to { @@ -333,21 +343,6 @@ value {}{}", } } -impl UIfmt for TransactionReceiptWithRevertReason { - fn pretty(&self) -> String { - if let Some(revert_reason) = &self.revert_reason { - format!( - "{} -revertReason {}", - self.receipt.pretty(), - revert_reason - ) - } else { - self.receipt.pretty() - } - } -} - /// Various numerical ethereum types used for pretty printing #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] @@ -404,38 +399,6 @@ pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option Option { - match attr { - "blockHash" | "block_hash" => Some(receipt.receipt.block_hash.pretty()), - "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), - "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), - "cumulativeGasUsed" | "cumulative_gas_used" => { - Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) - } - "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.to_string()) - } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), - "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), - "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root.pretty()), - "status" | "statusCode" | "status_code" => { - Some(pretty_status(receipt.receipt.inner.inner.inner.receipt.status.coerce_status())) - } - "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), - "transactionIndex" | "transaction_index" => { - Some(receipt.receipt.transaction_index.pretty()) - } - "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), - "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), - _ => None, - } -} - /// Returns the `UiFmt::pretty()` formatted attribute of the given block pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { match attr { diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index 6b7615b39..a7c545bc8 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -198,8 +198,7 @@ mod tests { let param0 = B256::random(); let param1 = vec![3; 32]; let param2 = B256::random(); - let log = - LogData::new_unchecked(vec![event.selector(), param0, param2], param1.clone().into()); + let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into()); let event = get_indexed_event(event, &log); assert_eq!(event.inputs.len(), 3); diff --git a/crates/common/src/calc.rs b/crates/common/src/calc.rs index bde75635c..2d7d6fb9e 100644 --- a/crates/common/src/calc.rs +++ b/crates/common/src/calc.rs @@ -1,7 +1,5 @@ //! Commonly used calculations. -use alloy_primitives::{Sign, U256}; - /// Returns the mean of the slice. #[inline] pub fn mean(values: &[u64]) -> u64 { @@ -28,41 +26,6 @@ pub fn median_sorted(values: &[u64]) -> u64 { } } -/// Returns the number expressed as a string in exponential notation -/// with the given precision (number of significant figures), -/// optionally removing trailing zeros from the mantissa. -/// -/// Examples: -/// -/// ```text -/// precision = 4, trim_end_zeroes = false -/// 1234124124 -> 1.234e9 -/// 10000000 -> 1.000e7 -/// precision = 3, trim_end_zeroes = true -/// 1234124124 -> 1.23e9 -/// 10000000 -> 1e7 -/// ``` -#[inline] -pub fn to_exp_notation(value: U256, precision: usize, trim_end_zeros: bool, sign: Sign) -> String { - let stringified = value.to_string(); - let exponent = stringified.len() - 1; - let mut mantissa = stringified.chars().take(precision).collect::(); - - // optionally remove trailing zeros - if trim_end_zeros { - mantissa = mantissa.trim_end_matches('0').to_string(); - } - - // Place a decimal point only if needed - // e.g. 1234 -> 1.234e3 (needed) - // 5 -> 5 (not needed) - if mantissa.len() > 1 { - mantissa.insert(1, '.'); - } - - format!("{sign}{mantissa}e{exponent}") -} - #[cfg(test)] mod tests { use super::*; @@ -106,23 +69,4 @@ mod tests { let m = median_sorted(&values); assert_eq!(m, 45); } - - #[test] - fn test_format_to_exponential_notation() { - let value = 1234124124u64; - - let formatted = to_exp_notation(U256::from(value), 4, false, Sign::Positive); - assert_eq!(formatted, "1.234e9"); - - let formatted = to_exp_notation(U256::from(value), 3, true, Sign::Positive); - assert_eq!(formatted, "1.23e9"); - - let value = 10000000u64; - - let formatted = to_exp_notation(U256::from(value), 4, false, Sign::Positive); - assert_eq!(formatted, "1.000e7"); - - let formatted = to_exp_notation(U256::from(value), 3, true, Sign::Positive); - assert_eq!(formatted, "1e7"); - } } diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 32d8395df..1468a44dd 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -7,11 +7,12 @@ use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ artifacts::{remappings::Remapping, BytecodeObject, ContractBytecodeSome, Libraries, Source}, compilers::{ - multi::MultiCompilerLanguage, solc::{Solc, SolcCompiler}, Compiler, }, + multi::MultiCompilerLanguage, report::{BasicStdoutReporter, NoReporter, Report}, + solc::SolcSettings, zksync::{ artifact_output::Artifact as ZkArtifact, compile::output::ProjectCompileOutput as ZkProjectCompileOutput, @@ -256,7 +257,7 @@ impl ProjectCompiler { let dev_functions = artifact.abi.as_ref().map(|abi| abi.functions()).into_iter().flatten().filter( |func| { - func.name.is_test() || + func.name.is_any_test() || func.name.eq("IS_TEST") || func.name.eq("IS_SCRIPT") }, @@ -412,9 +413,8 @@ impl ProjectCompiler { let mut abs_path_buf = PathBuf::new(); abs_path_buf.push(root_path.as_ref()); abs_path_buf.push(contract_path); - let abs_path_str = abs_path_buf.to_string_lossy(); - let art = output.find(abs_path_str, contract_name).unwrap_or_else(|| { + let art = output.find(abs_path_buf.as_path(), contract_name).unwrap_or_else(|| { panic!( "Could not find contract {contract_name} at path {contract_path} for compilation output" ) @@ -466,7 +466,16 @@ impl ProjectCompiler { } let mut size_report = SizeReport { contracts: BTreeMap::new(), zksync: self.zksync }; - let artifacts: BTreeMap<_, _> = output.artifacts().collect(); + + let artifacts: BTreeMap<_, _> = output + .artifact_ids() + .filter(|(id, _)| { + // filter out forge-std specific contracts + !id.source.to_string_lossy().contains("/forge-std/src/") + }) + .map(|(id, artifact)| (id.name, artifact)) + .collect(); + for (name, artifact) in artifacts { let bytecode = artifact.get_bytecode_object().unwrap_or_default(); let size = match bytecode.as_ref() { @@ -477,16 +486,16 @@ impl ProjectCompiler { } }; - let dev_functions = - artifact.abi.as_ref().map(|abi| abi.functions()).into_iter().flatten().filter( - |func| { - func.name.is_test() || - func.name.eq("IS_TEST") || - func.name.eq("IS_SCRIPT") - }, - ); - - let is_dev_contract = dev_functions.count() > 0; + let is_dev_contract = artifact + .abi + .as_ref() + .map(|abi| { + abi.functions().any(|f| { + f.test_function_kind().is_known() || + matches!(f.name.as_str(), "IS_TEST" | "IS_SCRIPT") + }) + }) + .unwrap_or(false); size_report.contracts.insert(name, ContractInfo { size, is_dev_contract }); } @@ -764,7 +773,7 @@ pub fn etherscan_project( let sources_path = target_path.join(&metadata.contract_name); metadata.source_tree().write_to(&target_path)?; - let mut settings = metadata.source_code.settings()?.unwrap_or_default(); + let mut settings = metadata.settings()?; // make remappings absolute with our root for remapping in settings.remappings.iter_mut() { @@ -796,7 +805,10 @@ pub fn etherscan_project( let compiler = SolcCompiler::Specific(solc); Ok(ProjectBuilder::::default() - .settings(SolcConfig::builder().settings(settings).build().settings) + .settings(SolcSettings { + settings: SolcConfig::builder().settings(settings).build().settings, + ..Default::default() + }) .paths(paths) .ephemeral() .no_artifacts() diff --git a/crates/common/src/console/HardhatConsole.json b/crates/common/src/console/HardhatConsole.json deleted file mode 100644 index 4013d8753..000000000 --- a/crates/common/src/console/HardhatConsole.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] \ No newline at end of file diff --git a/crates/common/src/console/hardhat_console.rs b/crates/common/src/console/hardhat_console.rs deleted file mode 100644 index e740990dd..000000000 --- a/crates/common/src/console/hardhat_console.rs +++ /dev/null @@ -1,567 +0,0 @@ -use alloy_primitives::Selector; -use alloy_sol_types::sol; -use foundry_macros::ConsoleFmt; -use once_cell::sync::Lazy; -use revm::primitives::HashMap; - -sol!( - #[sol(abi)] - #[derive(ConsoleFmt)] - #[allow(missing_docs)] - HardhatConsole, - "src/console/HardhatConsole.json" -); - -/// 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 Solc (and [`sol!`]) uses. -pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: Lazy> = Lazy::new(|| { - HashMap::from([ - // log(bool,uint256,uint256,address) - ([241, 97, 178, 33], [0, 221, 135, 185]), - // log(uint256,address,address,string) - ([121, 67, 220, 102], [3, 28, 111, 115]), - // log(uint256,bool,address,uint256) - ([65, 181, 239, 59], [7, 130, 135, 245]), - // log(bool,address,bool,uint256) - ([76, 182, 15, 209], [7, 131, 21, 2]), - // log(bool,uint256,address) - ([196, 210, 53, 7], [8, 142, 249, 210]), - // log(uint256,address,address,bool) - ([1, 85, 11, 4], [9, 31, 250, 245]), - // log(address,bool,uint256,string) - ([155, 88, 142, 204], [10, 166, 207, 173]), - // log(bool,bool,uint256,uint256) - ([70, 103, 222, 142], [11, 176, 14, 171]), - // log(bool,address,address,uint256) - ([82, 132, 189, 108], [12, 102, 209, 190]), - // log(uint256,address,uint256,uint256) - ([202, 154, 62, 180], [12, 156, 217, 193]), - // log(string,address,uint256) - ([7, 200, 18, 23], [13, 38, 185, 37]), - // log(address,string,uint256,bool) - ([126, 37, 13, 91], [14, 247, 224, 80]), - // log(address,uint256,address,uint256) - ([165, 217, 135, 104], [16, 15, 101, 14]), - // log(string,string,uint256,address) - ([93, 79, 70, 128], [16, 35, 247, 178]), - // log(bool,string,uint256) - ([192, 56, 42, 172], [16, 147, 238, 17]), - // log(bool,bool,uint256) - ([176, 19, 101, 187], [18, 242, 22, 2]), - // log(bool,address,uint256,address) - ([104, 241, 88, 181], [19, 107, 5, 221]), - // log(bool,uint256,address,uint256) - ([202, 165, 35, 106], [21, 55, 220, 135]), - // log(bool,string,uint256,address) - ([91, 34, 185, 56], [21, 150, 161, 206]), - // log(address,string,string,uint256) - ([161, 79, 208, 57], [21, 159, 137, 39]), - // log(uint256,address,uint256,address) - ([253, 178, 236, 212], [21, 193, 39, 181]), - // log(uint256,uint256,address,bool) - ([168, 232, 32, 174], [21, 202, 196, 118]), - // log(bool,string,bool,uint256) - ([141, 111, 156, 165], [22, 6, 163, 147]), - // log(address,address,uint256) - ([108, 54, 109, 114], [23, 254, 97, 133]), - // log(uint256,uint256,uint256,uint256) - ([92, 160, 173, 62], [25, 63, 184, 0]), - // log(bool,string,uint256,string) - ([119, 161, 171, 237], [26, 217, 109, 230]), - // log(bool,uint256,address,string) - ([24, 9, 19, 65], [27, 179, 176, 154]), - // log(string,uint256,address) - ([227, 132, 159, 121], [28, 126, 196, 72]), - // log(uint256,bool) - ([30, 109, 212, 236], [28, 157, 126, 179]), - // log(address,uint256,address,string) - ([93, 113, 243, 158], [29, 169, 134, 234]), - // log(address,string,uint256,uint256) - ([164, 201, 42, 96], [29, 200, 225, 184]), - // log(uint256,bool,uint256) - ([90, 77, 153, 34], [32, 9, 128, 20]), - // log(uint256,bool,bool) - ([213, 206, 172, 224], [32, 113, 134, 80]), - // log(address,uint256,uint256,address) - ([30, 246, 52, 52], [32, 227, 152, 77]), - // log(uint256,string,string,string) - ([87, 221, 10, 17], [33, 173, 6, 131]), - // log(address,uint256,bool,uint256) - ([105, 143, 67, 146], [34, 246, 185, 153]), - // log(uint256,address,address,address) - ([85, 71, 69, 249], [36, 136, 180, 20]), - // log(string,bool,string,uint256) - ([52, 203, 48, 141], [36, 249, 20, 101]), - // log(bool,uint256,address,address) - ([138, 47, 144, 170], [38, 245, 96, 168]), - // log(uint256,uint256,string,string) - ([124, 3, 42, 50], [39, 216, 175, 210]), - // log(bool,string,uint256,uint256) - ([142, 74, 232, 110], [40, 134, 63, 203]), - // log(uint256,bool,string,uint256) - ([145, 95, 219, 40], [44, 29, 7, 70]), - // log(address,uint256,uint256,uint256) - ([61, 14, 157, 228], [52, 240, 230, 54]), - // log(uint256,bool,address) - ([66, 78, 255, 191], [53, 8, 95, 123]), - // log(string,uint256,bool,bool) - ([227, 127, 243, 208], [53, 76, 54, 214]), - // log(bool,uint256,uint256) - ([59, 92, 3, 224], [55, 16, 51, 103]), - // log(bool,uint256,uint256,uint256) - ([50, 223, 165, 36], [55, 75, 180, 178]), - // log(uint256,string,uint256) - ([91, 109, 232, 63], [55, 170, 125, 76]), - // log(address,bool,uint256,uint256) - ([194, 16, 160, 30], [56, 111, 245, 244]), - // log(address,address,bool,uint256) - ([149, 214, 95, 17], [57, 113, 231, 140]), - // log(bool,uint256) - ([54, 75, 106, 146], [57, 145, 116, 211]), - // log(uint256,string,uint256,address) - ([171, 123, 217, 253], [59, 34, 121, 180]), - // log(address,uint256,bool,bool) - ([254, 161, 213, 90], [59, 245, 229, 55]), - // log(uint256,address,string,string) - ([141, 119, 134, 36], [62, 18, 140, 163]), - // log(string,address,bool,uint256) - ([197, 209, 187, 139], [62, 159, 134, 106]), - // log(uint256,uint256,string,address) - ([67, 50, 133, 162], [66, 210, 29, 183]), - // log(address,string,uint256,string) - ([93, 19, 101, 201], [68, 136, 48, 168]), - // log(uint256,bool,address,bool) - ([145, 251, 18, 66], [69, 77, 84, 165]), - // log(address,string,address,uint256) - ([140, 25, 51, 169], [69, 127, 227, 207]), - // log(uint256,address,string,uint256) - ([160, 196, 20, 232], [70, 130, 107, 93]), - // log(uint256,uint256,bool) - ([103, 87, 15, 247], [71, 102, 218, 114]), - // log(address,uint256,address,address) - ([236, 36, 132, 111], [71, 141, 28, 98]), - // log(address,uint256,uint256,string) - ([137, 52, 13, 171], [74, 40, 192, 23]), - // log(bool,bool,address,uint256) - ([96, 147, 134, 231], [76, 18, 61, 87]), - // log(uint256,string,bool) - ([70, 167, 208, 206], [76, 237, 167, 90]), - // log(string,uint256,address,uint256) - ([88, 73, 122, 254], [79, 4, 253, 198]), - // log(address,string,bool,uint256) - ([231, 32, 82, 28], [81, 94, 56, 182]), - // log(bool,address,uint256,string) - ([160, 104, 88, 51], [81, 240, 159, 248]), - // log(bool,bool,uint256,address) - ([11, 255, 149, 13], [84, 167, 169, 160]), - // log(uint256,uint256,address,address) - ([202, 147, 155, 32], [86, 165, 209, 177]), - // log(string,string,uint256) - ([243, 98, 202, 89], [88, 33, 239, 161]), - // log(string,uint256,string) - ([163, 245, 199, 57], [89, 112, 224, 137]), - // log(uint256,uint256,uint256,string) - ([120, 173, 122, 12], [89, 207, 203, 227]), - // log(string,address,uint256,string) - ([76, 85, 242, 52], [90, 71, 118, 50]), - // log(uint256,address,uint256) - ([136, 67, 67, 170], [90, 155, 94, 213]), - // log(string,uint256,string,string) - ([108, 152, 218, 226], [90, 184, 78, 31]), - // log(uint256,address,bool,uint256) - ([123, 8, 232, 235], [90, 189, 153, 42]), - // log(address,uint256,string,address) - ([220, 121, 38, 4], [92, 67, 13, 71]), - // log(uint256,uint256,address) - ([190, 51, 73, 27], [92, 150, 179, 49]), - // log(string,bool,address,uint256) - ([40, 223, 78, 150], [93, 8, 187, 5]), - // log(string,string,uint256,string) - ([141, 20, 44, 221], [93, 26, 151, 26]), - // log(uint256,uint256,string,uint256) - ([56, 148, 22, 61], [93, 162, 151, 235]), - // log(string,uint256,address,address) - ([234, 200, 146, 129], [94, 162, 183, 174]), - // log(uint256,address,uint256,bool) - ([25, 246, 115, 105], [95, 116, 58, 124]), - // log(bool,address,uint256) - ([235, 112, 75, 175], [95, 123, 154, 251]), - // log(uint256,string,address,address) - ([127, 165, 69, 139], [97, 104, 237, 97]), - // log(bool,bool,uint256,bool) - ([171, 92, 193, 196], [97, 158, 77, 14]), - // log(address,string,uint256,address) - ([223, 215, 216, 11], [99, 24, 54, 120]), - // log(uint256,address,string) - ([206, 131, 4, 123], [99, 203, 65, 249]), - // log(string,address,uint256,address) - ([163, 102, 236, 128], [99, 251, 139, 197]), - // log(uint256,string) - ([15, 163, 243, 69], [100, 63, 208, 223]), - // log(string,bool,uint256,uint256) - ([93, 191, 240, 56], [100, 181, 187, 103]), - // log(address,uint256,uint256,bool) - ([236, 75, 168, 162], [102, 241, 188, 103]), - // log(address,uint256,bool) - ([229, 74, 225, 68], [103, 130, 9, 168]), - // log(address,string,uint256) - ([28, 218, 242, 138], [103, 221, 111, 241]), - // log(uint256,bool,string,string) - ([164, 51, 252, 253], [104, 200, 184, 189]), - // log(uint256,string,uint256,bool) - ([135, 90, 110, 46], [105, 26, 143, 116]), - // log(uint256,address) - ([88, 235, 134, 12], [105, 39, 108, 134]), - // log(uint256,bool,bool,address) - ([83, 6, 34, 93], [105, 100, 11, 89]), - // log(bool,uint256,string,uint256) - ([65, 128, 1, 27], [106, 17, 153, 226]), - // log(bool,string,uint256,bool) - ([32, 187, 201, 175], [107, 14, 93, 83]), - // log(uint256,uint256,address,string) - ([214, 162, 209, 222], [108, 222, 64, 184]), - // log(bool,bool,bool,uint256) - ([194, 72, 131, 77], [109, 112, 69, 193]), - // log(uint256,uint256,string) - ([125, 105, 14, 230], [113, 208, 74, 242]), - // log(uint256,address,address,uint256) - ([154, 60, 191, 150], [115, 110, 251, 182]), - // log(string,bool,uint256,string) - ([66, 185, 162, 39], [116, 45, 110, 231]), - // log(uint256,bool,bool,uint256) - ([189, 37, 173, 89], [116, 100, 206, 35]), - // log(string,uint256,uint256,bool) - ([247, 60, 126, 61], [118, 38, 219, 146]), - // log(uint256,uint256,string,bool) - ([178, 46, 175, 6], [122, 246, 171, 37]), - // log(uint256,string,address) - ([31, 144, 242, 74], [122, 250, 201, 89]), - // log(address,uint256,address) - ([151, 236, 163, 148], [123, 192, 216, 72]), - // log(bool,string,string,uint256) - ([93, 219, 37, 146], [123, 224, 195, 235]), - // log(bool,address,uint256,uint256) - ([155, 254, 114, 188], [123, 241, 129, 161]), - // log(string,uint256,string,address) - ([187, 114, 53, 233], [124, 70, 50, 164]), - // log(string,string,address,uint256) - ([74, 129, 165, 106], [124, 195, 198, 7]), - // log(string,uint256,string,bool) - ([233, 159, 130, 207], [125, 36, 73, 29]), - // log(bool,bool,uint256,string) - ([80, 97, 137, 55], [125, 212, 208, 224]), - // log(bool,uint256,bool,uint256) - ([211, 222, 85, 147], [127, 155, 188, 162]), - // log(address,bool,string,uint256) - ([158, 18, 123, 110], [128, 230, 162, 11]), - // log(string,uint256,address,bool) - ([17, 6, 168, 247], [130, 17, 42, 66]), - // log(uint256,string,uint256,uint256) - ([192, 4, 56, 7], [130, 194, 91, 116]), - // log(address,uint256) - ([34, 67, 207, 163], [131, 9, 232, 168]), - // log(string,uint256,uint256,string) - ([165, 78, 212, 189], [133, 75, 52, 150]), - // log(uint256,bool,string) - ([139, 14, 20, 254], [133, 119, 80, 33]), - // log(address,uint256,string,string) - ([126, 86, 198, 147], [136, 168, 196, 6]), - // log(uint256,bool,uint256,address) - ([79, 64, 5, 142], [136, 203, 96, 65]), - // log(uint256,uint256,address,uint256) - ([97, 11, 168, 192], [136, 246, 228, 178]), - // log(string,bool,uint256,bool) - ([60, 197, 181, 211], [138, 247, 207, 138]), - // log(address,bool,bool,uint256) - ([207, 181, 135, 86], [140, 78, 93, 230]), - // log(address,address,uint256,address) - ([214, 198, 82, 118], [141, 166, 222, 245]), - // log(string,bool,bool,uint256) - ([128, 117, 49, 232], [142, 63, 120, 169]), - // log(bool,uint256,uint256,string) - ([218, 6, 102, 200], [142, 105, 251, 93]), - // log(string,string,string,uint256) - ([159, 208, 9, 245], [142, 175, 176, 43]), - // log(string,address,address,uint256) - ([110, 183, 148, 61], [142, 243, 243, 153]), - // log(uint256,string,address,bool) - ([249, 63, 255, 55], [144, 195, 10, 86]), - // log(uint256,address,bool,string) - ([99, 240, 226, 66], [144, 251, 6, 170]), - // log(bool,uint256,bool,string) - ([182, 213, 105, 212], [145, 67, 219, 177]), - // log(uint256,bool,uint256,bool) - ([210, 171, 196, 253], [145, 160, 46, 42]), - // log(string,address,string,uint256) - ([143, 98, 75, 233], [145, 209, 17, 46]), - // log(string,bool,uint256,address) - ([113, 211, 133, 13], [147, 94, 9, 191]), - // log(address,address,address,uint256) - ([237, 94, 172, 135], [148, 37, 13, 119]), - // log(uint256,uint256,bool,address) - ([225, 23, 116, 79], [154, 129, 106, 131]), - // log(bool,uint256,bool,address) - ([66, 103, 199, 248], [154, 205, 54, 22]), - // log(address,address,uint256,bool) - ([194, 246, 136, 236], [155, 66, 84, 226]), - // log(uint256,address,bool) - ([122, 208, 18, 142], [155, 110, 192, 66]), - // log(uint256,string,address,string) - ([248, 152, 87, 127], [156, 58, 223, 161]), - // log(address,bool,uint256) - ([44, 70, 141, 21], [156, 79, 153, 251]), - // log(uint256,address,string,address) - ([203, 229, 142, 253], [156, 186, 143, 255]), - // log(string,uint256,address,string) - ([50, 84, 194, 232], [159, 251, 47, 147]), - // log(address,uint256,address,bool) - ([241, 129, 161, 233], [161, 188, 201, 179]), - // log(uint256,bool,address,address) - ([134, 237, 193, 12], [161, 239, 76, 187]), - // log(address,uint256,string) - ([186, 249, 104, 73], [161, 242, 232, 170]), - // log(address,uint256,bool,address) - ([35, 229, 73, 114], [163, 27, 253, 204]), - // log(uint256,uint256,bool,string) - ([239, 217, 203, 238], [165, 180, 252, 153]), - // log(bool,string,address,uint256) - ([27, 11, 149, 91], [165, 202, 218, 148]), - // log(address,bool,address,uint256) - ([220, 113, 22, 210], [167, 92, 89, 222]), - // log(string,uint256,uint256,uint256) - ([8, 238, 86, 102], [167, 168, 120, 83]), - // log(uint256,uint256,bool,bool) - ([148, 190, 59, 177], [171, 8, 90, 230]), - // log(string,uint256,bool,string) - ([118, 204, 96, 100], [171, 247, 58, 152]), - // log(uint256,bool,address,string) - ([162, 48, 118, 30], [173, 224, 82, 199]), - // log(uint256,string,bool,address) - ([121, 111, 40, 160], [174, 46, 197, 129]), - // log(uint256,string,string,uint256) - ([118, 236, 99, 94], [176, 40, 201, 189]), - // log(uint256,string,string) - ([63, 87, 194, 149], [177, 21, 97, 31]), - // log(uint256,string,string,bool) - ([18, 134, 43, 152], [179, 166, 182, 189]), - // log(bool,uint256,address,bool) - ([101, 173, 244, 8], [180, 195, 20, 255]), - // log(string,uint256) - ([151, 16, 169, 208], [182, 14, 114, 204]), - // log(address,uint256,uint256) - ([135, 134, 19, 94], [182, 155, 202, 246]), - // log(uint256,bool,bool,bool) - ([78, 108, 83, 21], [182, 245, 119, 161]), - // log(uint256,string,uint256,string) - ([162, 188, 12, 153], [183, 185, 20, 202]), - // log(uint256,string,bool,bool) - ([81, 188, 43, 193], [186, 83, 93, 156]), - // log(uint256,address,address) - ([125, 119, 166, 27], [188, 253, 155, 224]), - // log(address,address,uint256,uint256) - ([84, 253, 243, 228], [190, 85, 52, 129]), - // log(bool,uint256,uint256,bool) - ([164, 29, 129, 222], [190, 152, 67, 83]), - // log(address,uint256,string,uint256) - ([245, 18, 207, 155], [191, 1, 248, 145]), - // log(bool,address,string,uint256) - ([11, 153, 252, 34], [194, 31, 100, 199]), - // log(string,string,uint256,bool) - ([230, 86, 88, 202], [195, 168, 166, 84]), - // log(bool,uint256,string) - ([200, 57, 126, 176], [195, 252, 57, 112]), - // log(address,bool,uint256,bool) - ([133, 205, 197, 175], [196, 100, 62, 32]), - // log(uint256,uint256,uint256,bool) - ([100, 82, 185, 203], [197, 152, 209, 133]), - // log(address,uint256,bool,string) - ([142, 142, 78, 117], [197, 173, 133, 249]), - // log(string,uint256,string,uint256) - ([160, 196, 178, 37], [198, 126, 169, 209]), - // log(uint256,bool,uint256,uint256) - ([86, 130, 141, 164], [198, 172, 199, 168]), - // log(string,bool,uint256) - ([41, 27, 185, 208], [201, 89, 88, 214]), - // log(string,uint256,uint256) - ([150, 156, 221, 3], [202, 71, 196, 235]), - // log(string,uint256,bool) - ([241, 2, 238, 5], [202, 119, 51, 177]), - // log(uint256,address,string,bool) - ([34, 164, 121, 166], [204, 50, 171, 7]), - // log(address,bool,uint256,address) - ([13, 140, 230, 30], [204, 247, 144, 161]), - // log(bool,uint256,bool,bool) - ([158, 1, 247, 65], [206, 181, 244, 215]), - // log(uint256,string,bool,uint256) - ([164, 180, 138, 127], [207, 0, 152, 128]), - // log(address,uint256,string,bool) - ([164, 2, 79, 17], [207, 24, 16, 92]), - // log(uint256,uint256,uint256) - ([231, 130, 10, 116], [209, 237, 122, 60]), - // log(uint256,string,bool,string) - ([141, 72, 156, 160], [210, 212, 35, 205]), - // log(uint256,string,string,address) - ([204, 152, 138, 160], [213, 131, 198, 2]), - // log(bool,address,uint256,bool) - ([238, 141, 134, 114], [214, 1, 159, 28]), - // log(string,string,bool,uint256) - ([134, 129, 138, 122], [214, 174, 250, 210]), - // log(uint256,address,uint256,string) - ([62, 211, 189, 40], [221, 176, 101, 33]), - // log(uint256,bool,bool,string) - ([49, 138, 229, 155], [221, 219, 149, 97]), - // log(uint256,bool,uint256,string) - ([232, 221, 188, 86], [222, 3, 231, 116]), - // log(string,uint256,bool,address) - ([229, 84, 157, 145], [224, 233, 91, 152]), - // log(string,uint256,uint256,address) - ([190, 215, 40, 191], [226, 29, 226, 120]), - // log(uint256,address,bool,bool) - ([126, 39, 65, 13], [227, 81, 20, 15]), - // log(bool,bool,string,uint256) - ([23, 139, 70, 133], [227, 169, 202, 47]), - // log(string,uint256,bool,uint256) - ([85, 14, 110, 245], [228, 27, 111, 111]), - // log(bool,uint256,string,bool) - ([145, 210, 248, 19], [229, 231, 11, 43]), - // log(uint256,string,address,uint256) - ([152, 231, 243, 243], [232, 211, 1, 141]), - // log(bool,uint256,bool) - ([27, 173, 201, 235], [232, 222, 251, 169]), - // log(uint256,uint256,bool,uint256) - ([108, 100, 124, 140], [235, 127, 111, 210]), - // log(uint256,bool,string,bool) - ([52, 110, 184, 199], [235, 146, 141, 127]), - // log(address,address,string,uint256) - ([4, 40, 147, 0], [239, 28, 239, 231]), - // log(uint256,bool,string,address) - ([73, 110, 43, 180], [239, 82, 144, 24]), - // log(uint256,address,bool,address) - ([182, 49, 48, 148], [239, 114, 197, 19]), - // log(string,string,uint256,uint256) - ([213, 207, 23, 208], [244, 93, 125, 44]), - // log(bool,uint256,string,string) - ([211, 42, 101, 72], [245, 188, 34, 73]), - // log(uint256,uint256) - ([108, 15, 105, 128], [246, 102, 113, 90]), - // log(uint256) and logUint(uint256) - ([245, 177, 187, 169], [248, 44, 80, 241]), - // log(string,address,uint256,uint256) - ([218, 163, 148, 189], [248, 245, 27, 30]), - // log(uint256,uint256,uint256,address) - ([224, 133, 63, 105], [250, 129, 133, 175]), - // log(string,address,uint256,bool) - ([90, 193, 193, 60], [252, 72, 69, 240]), - // log(address,address,uint256,string) - ([157, 209, 46, 173], [253, 180, 249, 144]), - // log(bool,uint256,string,address) - ([165, 199, 13, 41], [254, 221, 31, 255]), - // logInt(int256) - ([78, 12, 29, 29], [101, 37, 181, 245]), - // logBytes(bytes) - ([11, 231, 127, 86], [225, 123, 249, 86]), - // logBytes1(bytes1) - ([110, 24, 161, 40], [111, 65, 113, 201]), - // logBytes2(bytes2) - ([233, 182, 34, 150], [155, 94, 148, 62]), - // logBytes3(bytes3) - ([45, 131, 73, 38], [119, 130, 250, 45]), - // logBytes4(bytes4) - ([224, 95, 72, 209], [251, 163, 173, 57]), - // logBytes5(bytes5) - ([166, 132, 128, 141], [85, 131, 190, 46]), - // logBytes6(bytes6) - ([174, 132, 165, 145], [73, 66, 173, 198]), - // logBytes7(bytes7) - ([78, 213, 126, 40], [69, 116, 175, 171]), - // logBytes8(bytes8) - ([79, 132, 37, 46], [153, 2, 228, 127]), - // logBytes9(bytes9) - ([144, 189, 140, 208], [80, 161, 56, 223]), - // logBytes10(bytes10) - ([1, 61, 23, 139], [157, 194, 168, 151]), - // logBytes11(bytes11) - ([4, 0, 74, 46], [220, 8, 182, 167]), - // logBytes12(bytes12) - ([134, 160, 106, 189], [118, 86, 214, 199]), - // logBytes13(bytes13) - ([148, 82, 158, 52], [52, 193, 216, 27]), - // logBytes14(bytes14) - ([146, 102, 240, 127], [60, 234, 186, 101]), - // logBytes15(bytes15) - ([218, 149, 116, 224], [89, 26, 61, 162]), - // logBytes16(bytes16) - ([102, 92, 97, 4], [31, 141, 115, 18]), - // logBytes17(bytes17) - ([51, 159, 103, 58], [248, 154, 83, 47]), - // logBytes18(bytes18) - ([196, 210, 61, 154], [216, 101, 38, 66]), - // logBytes19(bytes19) - ([94, 107, 90, 51], [0, 245, 107, 201]), - // logBytes20(bytes20) - ([81, 136, 227, 233], [236, 184, 86, 126]), - // logBytes21(bytes21) - ([233, 218, 53, 96], [48, 82, 192, 143]), - // logBytes22(bytes22) - ([213, 250, 232, 156], [128, 122, 180, 52]), - // logBytes23(bytes23) - ([171, 161, 207, 13], [73, 121, 176, 55]), - // logBytes24(bytes24) - ([241, 179, 91, 52], [9, 119, 174, 252]), - // logBytes25(bytes25) - ([11, 132, 188, 88], [174, 169, 150, 63]), - // logBytes26(bytes26) - ([248, 177, 73, 241], [211, 99, 86, 40]), - // logBytes27(bytes27) - ([58, 55, 87, 221], [252, 55, 47, 159]), - // logBytes28(bytes28) - ([200, 42, 234, 238], [56, 47, 154, 52]), - // logBytes29(bytes29) - ([75, 105, 195, 213], [122, 24, 118, 65]), - // logBytes30(bytes30) - ([238, 18, 196, 237], [196, 52, 14, 246]), - // logBytes31(bytes31) - ([194, 133, 77, 146], [129, 252, 134, 72]), - // logBytes32(bytes32) - ([39, 183, 207, 133], [45, 33, 214, 247]), - ]) -}); - -#[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/common/src/console/interface.rs b/crates/common/src/console/interface.rs deleted file mode 100644 index 8022b1d44..000000000 --- a/crates/common/src/console/interface.rs +++ /dev/null @@ -1,93 +0,0 @@ -use alloy_primitives::{hex, I256, U256}; -use alloy_sol_types::sol; -use derive_more::Display; -use itertools::Itertools; - -// TODO: Use `UiFmt` - -sol! { -#[sol(abi)] -#[derive(Display)] -#[allow(missing_docs)] -interface Console { - #[display(fmt = "{val}")] - event log(string val); - - #[display(fmt = "{}", "hex::encode_prefixed(val)")] - event logs(bytes val); - - #[display(fmt = "{val}")] - event log_address(address val); - - #[display(fmt = "{val}")] - event log_bytes32(bytes32 val); - - #[display(fmt = "{val}")] - event log_int(int val); - - #[display(fmt = "{val}")] - event log_uint(uint val); - - #[display(fmt = "{}", "hex::encode_prefixed(val)")] - event log_bytes(bytes val); - - #[display(fmt = "{val}")] - event log_string(string val); - - #[display(fmt = "[{}]", "val.iter().format(\", \")")] - event log_array(uint256[] val); - - #[display(fmt = "[{}]", "val.iter().format(\", \")")] - event log_array(int256[] val); - - #[display(fmt = "[{}]", "val.iter().format(\", \")")] - event log_array(address[] val); - - #[display(fmt = "{key}: {val}")] - event log_named_address(string key, address val); - - #[display(fmt = "{key}: {val}")] - event log_named_bytes32(string key, bytes32 val); - - #[display(fmt = "{key}: {}", "format_units_int(val, decimals)")] - event log_named_decimal_int(string key, int val, uint decimals); - - #[display(fmt = "{key}: {}", "format_units_uint(val, decimals)")] - event log_named_decimal_uint(string key, uint val, uint decimals); - - #[display(fmt = "{key}: {val}")] - event log_named_int(string key, int val); - - #[display(fmt = "{key}: {val}")] - event log_named_uint(string key, uint val); - - #[display(fmt = "{key}: {}", "hex::encode_prefixed(val)")] - event log_named_bytes(string key, bytes val); - - #[display(fmt = "{key}: {val}")] - event log_named_string(string key, string val); - - #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] - event log_named_array(string key, uint256[] val); - - #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] - event log_named_array(string key, int256[] val); - - #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] - event log_named_array(string key, address[] val); -} -} - -#[allow(missing_docs)] -pub fn format_units_int(x: &I256, decimals: &U256) -> String { - let (sign, x) = x.into_sign_and_abs(); - format!("{sign}{}", format_units_uint(&x, decimals)) -} - -#[allow(missing_docs)] -pub fn format_units_uint(x: &U256, decimals: &U256) -> String { - match alloy_primitives::utils::Unit::new(decimals.saturating_to::()) { - Some(units) => alloy_primitives::utils::ParseUnits::U256(*x).format_units(units), - None => x.to_string(), - } -} diff --git a/crates/common/src/console/mod.rs b/crates/common/src/console/mod.rs deleted file mode 100644 index e03ad8323..000000000 --- a/crates/common/src/console/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Several ABI-related utilities for executors. - -use alloy_primitives::{address, Address}; -// pub use foundry_cheatcodes_spec::Vm; - -mod interface; -pub use interface::{format_units_int, format_units_uint, Console}; - -mod hardhat_console; -pub use hardhat_console::{ - hh_console_selector, patch_hh_console_selector, HardhatConsole, - HARDHAT_CONSOLE_SELECTOR_PATCHES, -}; - -/// The Hardhat console address. -/// -/// See: -pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 046a07425..7e07f9db6 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -88,9 +88,6 @@ pub struct ContractsByArtifact(Arc>); impl ContractsByArtifact { /// Creates a new instance by collecting all artifacts with present bytecode from an iterator. - /// - /// It is recommended to use this method with an output of - /// [foundry_linking::Linker::get_linked_artifacts]. pub fn new(artifacts: impl IntoIterator) -> Self { let map = artifacts .into_iter() @@ -118,29 +115,40 @@ impl ContractsByArtifact { /// Finds a contract which has a similar bytecode as `code`. pub fn find_by_creation_code(&self, code: &[u8]) -> Option> { - self.iter().find(|(_, contract)| { - if let Some(bytecode) = contract.bytecode() { - bytecode_diff_score(bytecode.as_ref(), code) <= 0.1 - } else { - false - } - }) + self.find_by_code(code, ContractData::bytecode) } /// Finds a contract which has a similar deployed bytecode as `code`. pub fn find_by_deployed_code(&self, code: &[u8]) -> Option> { - self.iter().find(|(_, contract)| { - if let Some(deployed_bytecode) = contract.deployed_bytecode() { - bytecode_diff_score(deployed_bytecode.as_ref(), code) <= 0.1 - } else { - false - } - }) + self.find_by_code(code, ContractData::deployed_bytecode) + } + + fn find_by_code( + &self, + code: &[u8], + get: impl Fn(&ContractData) -> Option<&Bytes>, + ) -> Option> { + self.iter() + .filter_map(|(id, contract)| { + if let Some(deployed_bytecode) = get(contract) { + let score = bytecode_diff_score(deployed_bytecode.as_ref(), code); + (score <= 0.1).then_some((score, (id, contract))) + } else { + None + } + }) + .min_by(|(score1, _), (score2, _)| score1.partial_cmp(score2).unwrap()) + .map(|(_, data)| data) } /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link /// references and immutables. pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option> { + // Immediately return None if the code is empty. + if code.is_empty() { + return None; + } + self.iter().find(|(_, contract)| { let Some(deployed_bytecode) = &contract.deployed_bytecode else { return false; @@ -397,4 +405,11 @@ mod tests { let a_99 = &b"a".repeat(99)[..]; assert!(bytecode_diff_score(a_100, a_99) <= 0.01); } + + #[test] + fn find_by_deployed_code_exact_with_empty_deployed() { + let contracts = ContractsByArtifact::new(vec![]); + + assert!(contracts.find_by_deployed_code_exact(&[]).is_none()); + } } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 979282e97..a33a7b223 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -11,16 +11,16 @@ extern crate self as foundry_common; #[macro_use] extern crate tracing; +pub use foundry_common_fmt as fmt; + pub mod abi; pub mod calc; pub mod compile; -pub mod console; pub mod constants; pub mod contracts; pub mod ens; pub mod errors; pub mod evm; -pub mod fmt; pub mod fs; pub mod provider; pub mod retry; @@ -32,7 +32,6 @@ pub mod traits; pub mod transactions; mod utils; -pub use console::*; pub use constants::*; pub use contracts::*; pub use traits::*; diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index ef7b62055..7bb943eac 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -1,8 +1,6 @@ //! Provider-related instantiation and usage utilities. -pub mod retry; pub mod runtime_transport; -pub mod tower; use crate::{ provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, @@ -13,7 +11,10 @@ use alloy_provider::{ Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, }; use alloy_rpc_client::ClientBuilder; -use alloy_transport::utils::guess_local_url; +use alloy_transport::{ + layers::{RetryBackoffLayer, RetryBackoffService}, + utils::guess_local_url, +}; use eyre::{Result, WrapErr}; use foundry_config::NamedChain; use reqwest::Url; @@ -24,7 +25,6 @@ use std::{ str::FromStr, time::Duration, }; -use tower::{RetryBackoffLayer, RetryBackoffService}; use url::ParseError; /// Helper type alias for a retry provider @@ -77,7 +77,6 @@ pub struct ProviderBuilder { url: Result, chain: NamedChain, max_retry: u32, - timeout_retry: u32, initial_backoff: u64, timeout: Duration, /// available CUPS @@ -128,7 +127,6 @@ impl ProviderBuilder { url, chain: NamedChain::Mainnet, max_retry: 8, - timeout_retry: 8, initial_backoff: 800, timeout: REQUEST_TIMEOUT, // alchemy max cpus @@ -175,12 +173,6 @@ impl ProviderBuilder { self } - /// How often to retry a failed request due to connection issues - pub fn timeout_retry(mut self, timeout_retry: u32) -> Self { - self.timeout_retry = timeout_retry; - self - } - /// The starting backoff delay to use after the first failed request pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { self.initial_backoff = initial_backoff; @@ -239,7 +231,6 @@ impl ProviderBuilder { url, chain: _, max_retry, - timeout_retry, initial_backoff, timeout, compute_units_per_second, @@ -249,13 +240,10 @@ impl ProviderBuilder { } = self; let url = url?; - let retry_layer = RetryBackoffLayer::new( - max_retry, - timeout_retry, - initial_backoff, - compute_units_per_second, - ); - let transport = RuntimeTransportBuilder::new(url.clone()) + let retry_layer = + RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); + + let transport = RuntimeTransportBuilder::new(url) .with_timeout(timeout) .with_headers(headers) .with_jwt(jwt) @@ -274,7 +262,6 @@ impl ProviderBuilder { url, chain: _, max_retry, - timeout_retry, initial_backoff, timeout, compute_units_per_second, @@ -284,14 +271,10 @@ impl ProviderBuilder { } = self; let url = url?; - let retry_layer = RetryBackoffLayer::new( - max_retry, - timeout_retry, - initial_backoff, - compute_units_per_second, - ); + let retry_layer = + RetryBackoffLayer::new(max_retry, initial_backoff, compute_units_per_second); - let transport = RuntimeTransportBuilder::new(url.clone()) + let transport = RuntimeTransportBuilder::new(url) .with_timeout(timeout) .with_headers(headers) .with_jwt(jwt) diff --git a/crates/common/src/provider/retry.rs b/crates/common/src/provider/retry.rs deleted file mode 100644 index b7f3079bb..000000000 --- a/crates/common/src/provider/retry.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! An utility trait for retrying requests based on the error type. See [TransportError]. -use alloy_json_rpc::ErrorPayload; -use alloy_transport::{TransportError, TransportErrorKind}; -use serde::Deserialize; - -/// [RetryPolicy] defines logic for which [TransportError] instances should -/// the client retry the request and try to recover from. -pub trait RetryPolicy: Send + Sync + std::fmt::Debug { - /// Whether to retry the request based on the given `error` - fn should_retry(&self, error: &TransportError) -> bool; - - /// Providers may include the `backoff` in the error response directly - fn backoff_hint(&self, error: &TransportError) -> Option; -} - -/// Implements [RetryPolicy] that will retry requests that errored with -/// status code 429 i.e. TOO_MANY_REQUESTS -/// -/// Infura often fails with a `"header not found"` rpc error which is apparently linked to load -/// balancing, which are retried as well. -#[derive(Clone, Debug, Default)] -pub struct RateLimitRetryPolicy; - -impl RetryPolicy for RateLimitRetryPolicy { - fn should_retry(&self, error: &TransportError) -> bool { - match error { - // There was a transport-level error. This is either a non-retryable error, - // or a server error that should be retried. - TransportError::Transport(err) => should_retry_transport_level_error(err), - // The transport could not serialize the error itself. The request was malformed from - // the start. - TransportError::SerError(_) => false, - TransportError::DeserError { text, .. } => should_retry_body(text), - TransportError::ErrorResp(err) => should_retry_json_rpc_error(err), - TransportError::NullResp => true, - TransportError::UnsupportedFeature(_) => false, - TransportError::LocalUsageError(_) => false, - } - } - - /// Provides a backoff hint if the error response contains it - fn backoff_hint(&self, error: &TransportError) -> Option { - if let TransportError::ErrorResp(resp) = error { - let data = resp.try_data_as::(); - if let Some(Ok(data)) = data { - // if daily rate limit exceeded, infura returns the requested backoff in the error - // response - let backoff_seconds = &data["rate"]["backoff_seconds"]; - // infura rate limit error - if let Some(seconds) = backoff_seconds.as_u64() { - return Some(std::time::Duration::from_secs(seconds)) - } - if let Some(seconds) = backoff_seconds.as_f64() { - return Some(std::time::Duration::from_secs(seconds as u64 + 1)) - } - } - } - None - } -} - -/// Tries to decode the error body as payload and check if it should be retried -fn should_retry_body(body: &str) -> bool { - if let Ok(resp) = serde_json::from_str::(body) { - return should_retry_json_rpc_error(&resp) - } - - // some providers send invalid JSON RPC in the error case (no `id:u64`), but the - // text should be a `JsonRpcError` - #[derive(Deserialize)] - struct Resp { - error: ErrorPayload, - } - - if let Ok(resp) = serde_json::from_str::(body) { - return should_retry_json_rpc_error(&resp.error) - } - - false -} - -/// Analyzes the [TransportErrorKind] and decides if the request should be retried based on the -/// variant. -fn should_retry_transport_level_error(error: &TransportErrorKind) -> bool { - match error { - // Missing batch response errors can be retried. - TransportErrorKind::MissingBatchResponse(_) => true, - TransportErrorKind::Custom(err) => { - // currently http error responses are not standard in alloy - let msg = err.to_string(); - msg.contains("429 Too Many Requests") - } - - TransportErrorKind::HttpError(err) => { - if err.status == 429 { - return true - } - should_retry_body(&err.body) - } - // If the backend is gone, or there's a completely custom error, we should assume it's not - // retryable. - TransportErrorKind::PubsubUnavailable => false, - TransportErrorKind::BackendGone => false, - _ => false, - } -} - -/// Analyzes the [ErrorPayload] and decides if the request should be retried based on the -/// error code or the message. -fn should_retry_json_rpc_error(error: &ErrorPayload) -> bool { - let ErrorPayload { code, message, .. } = error; - // alchemy throws it this way - if *code == 429 { - return true - } - - // This is an infura error code for `exceeded project rate limit` - if *code == -32005 { - return true - } - - // alternative alchemy error for specific IPs - if *code == -32016 && message.contains("rate limit") { - return true - } - - // quick node error `"credits limited to 6000/sec"` - // - if *code == -32012 && message.contains("credits") { - return true - } - - // quick node rate limit error: `100/second request limit reached - reduce calls per second or - // upgrade your account at quicknode.com` - if *code == -32007 && message.contains("request limit reached") { - return true - } - - match message.as_str() { - // this is commonly thrown by infura and is apparently a load balancer issue, see also - "header not found" => true, - // also thrown by infura if out of budget for the day and ratelimited - "daily request count exceeded, request rate limited" => true, - msg => { - msg.contains("rate limit") || - msg.contains("rate exceeded") || - msg.contains("too many requests") || - msg.contains("credits limited") || - msg.contains("request limit") - } - } -} diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 72c4dadc9..8ca90ca80 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -4,7 +4,7 @@ use crate::REQUEST_TIMEOUT; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_pubsub::{PubSubConnect, PubSubFrontend}; -use alloy_rpc_types_engine::{Claims, JwtSecret}; +use alloy_rpc_types::engine::{Claims, JwtSecret}; use alloy_transport::{ Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, }; diff --git a/crates/common/src/provider/tower.rs b/crates/common/src/provider/tower.rs deleted file mode 100644 index 73088021d..000000000 --- a/crates/common/src/provider/tower.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Alloy-related tower middleware for retrying rate-limited requests -//! and applying backoff. -use std::{ - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, - }, - task::{Context, Poll}, -}; - -use alloy_json_rpc::{RequestPacket, ResponsePacket}; -use alloy_transport::{TransportError, TransportErrorKind, TransportFut}; - -use super::{ - retry::{RateLimitRetryPolicy, RetryPolicy}, - runtime_transport::RuntimeTransport, -}; - -/// An Alloy Tower Layer that is responsible for retrying requests based on the -/// error type. See [TransportError]. -#[derive(Debug, Clone)] -pub struct RetryBackoffLayer { - /// The maximum number of retries for rate limit errors - max_rate_limit_retries: u32, - /// The maximum number of retries for timeout errors - max_timeout_retries: u32, - /// The initial backoff in milliseconds - initial_backoff: u64, - /// The number of compute units per second for this provider - compute_units_per_second: u64, -} - -impl RetryBackoffLayer { - /// Creates a new retry layer with the given parameters. - pub fn new( - max_rate_limit_retries: u32, - max_timeout_retries: u32, - initial_backoff: u64, - compute_units_per_second: u64, - ) -> Self { - Self { - max_rate_limit_retries, - max_timeout_retries, - initial_backoff, - compute_units_per_second, - } - } -} - -impl tower::layer::Layer for RetryBackoffLayer { - type Service = RetryBackoffService; - - fn layer(&self, inner: S) -> Self::Service { - RetryBackoffService { - inner, - policy: RateLimitRetryPolicy, - max_rate_limit_retries: self.max_rate_limit_retries, - _max_timeout_retries: self.max_timeout_retries, - initial_backoff: self.initial_backoff, - compute_units_per_second: self.compute_units_per_second, - requests_enqueued: Arc::new(AtomicU32::new(0)), - } - } -} - -/// An Alloy Tower Service that is responsible for retrying requests based on the -/// error type. See [TransportError] and [RateLimitRetryPolicy]. -#[derive(Debug, Clone)] -pub struct RetryBackoffService { - /// The inner service - inner: S, - /// The retry policy - policy: RateLimitRetryPolicy, - /// The maximum number of retries for rate limit errors - max_rate_limit_retries: u32, - /// The maximum number of retries for timeout errors - _max_timeout_retries: u32, - /// The initial backoff in milliseconds - initial_backoff: u64, - /// The number of compute units per second for this service - compute_units_per_second: u64, - /// The number of requests currently enqueued - requests_enqueued: Arc, -} - -// impl tower service -impl tower::Service for RetryBackoffService { - type Response = ResponsePacket; - type Error = TransportError; - type Future = TransportFut<'static>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - // Our middleware doesn't care about backpressure, so it's ready as long - // as the inner service is ready. - self.inner.poll_ready(cx) - } - - fn call(&mut self, request: RequestPacket) -> Self::Future { - let mut this = self.clone(); - Box::pin(async move { - let ahead_in_queue = this.requests_enqueued.fetch_add(1, Ordering::SeqCst) as u64; - let mut rate_limit_retry_number: u32 = 0; - loop { - let err; - let fut = this.inner.call(request.clone()).await; - - match fut { - Ok(res) => { - if let Some(e) = res.as_error() { - err = TransportError::ErrorResp(e.clone()) - } else { - this.requests_enqueued.fetch_sub(1, Ordering::SeqCst); - return Ok(res) - } - } - Err(e) => err = e, - } - - let should_retry = this.policy.should_retry(&err); - if should_retry { - rate_limit_retry_number += 1; - if rate_limit_retry_number > this.max_rate_limit_retries { - return Err(TransportErrorKind::custom_str("Max retries exceeded")) - } - trace!("retrying request due to {:?}", err); - - let current_queued_reqs = this.requests_enqueued.load(Ordering::SeqCst) as u64; - - // try to extract the requested backoff from the error or compute the next - // backoff based on retry count - let backoff_hint = this.policy.backoff_hint(&err); - let next_backoff = backoff_hint - .unwrap_or_else(|| std::time::Duration::from_millis(this.initial_backoff)); - - // requests are usually weighted and can vary from 10 CU to several 100 CU, - // cheaper requests are more common some example alchemy - // weights: - // - `eth_getStorageAt`: 17 - // - `eth_getBlockByNumber`: 16 - // - `eth_newFilter`: 20 - // - // (coming from forking mode) assuming here that storage request will be the - // driver for Rate limits we choose `17` as the average cost - // of any request - const AVG_COST: u64 = 17u64; - let seconds_to_wait_for_compute_budget = compute_unit_offset_in_secs( - AVG_COST, - this.compute_units_per_second, - current_queued_reqs, - ahead_in_queue, - ); - let total_backoff = next_backoff + - std::time::Duration::from_secs(seconds_to_wait_for_compute_budget); - - trace!(?total_backoff, budget_backoff = ?seconds_to_wait_for_compute_budget, default_backoff = ?next_backoff, ?backoff_hint, "backing off due to rate limit"); - - tokio::time::sleep(total_backoff).await; - } else { - trace!("encountered non retryable error {err:?}"); - this.requests_enqueued.fetch_sub(1, Ordering::SeqCst); - return Err(err) - } - } - }) - } -} - -/// Calculates an offset in seconds by taking into account the number of currently queued requests, -/// number of requests that were ahead in the queue when the request was first issued, the average -/// cost a weighted request (heuristic), and the number of available compute units per seconds. -/// -/// Returns the number of seconds (the unit the remote endpoint measures compute budget) a request -/// is supposed to wait to not get rate limited. The budget per second is -/// `compute_units_per_second`, assuming an average cost of `avg_cost` this allows (in theory) -/// `compute_units_per_second / avg_cost` requests per seconds without getting rate limited. -/// By taking into account the number of concurrent request and the position in queue when the -/// request was first issued and determine the number of seconds a request is supposed to wait, if -/// at all -fn compute_unit_offset_in_secs( - avg_cost: u64, - compute_units_per_second: u64, - current_queued_requests: u64, - ahead_in_queue: u64, -) -> u64 { - let request_capacity_per_second = compute_units_per_second.saturating_div(avg_cost); - if current_queued_requests > request_capacity_per_second { - current_queued_requests.min(ahead_in_queue).saturating_div(request_capacity_per_second) - } else { - 0 - } -} diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index 1f8949aa0..7f649c7ed 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -1,8 +1,17 @@ //! Retry utilities. -use eyre::{Error, Result}; +use eyre::{Error, Report, Result}; use std::{future::Future, time::Duration}; +/// Error type for Retry. +#[derive(Debug, thiserror::Error)] +pub enum RetryError { + /// Keeps retrying operation. + Retry(E), + /// Stops retrying operation immediately. + Break(E), +} + /// A type that keeps track of attempts. #[derive(Clone, Debug)] pub struct Retry { @@ -51,6 +60,27 @@ impl Retry { } } + /// Runs the given async closure in a loop, retrying if it fails up to the specified number of + /// times or immediately returning an error if the closure returned [`RetryError::Break`]. + pub async fn run_async_until_break(mut self, mut callback: F) -> Result + where + F: FnMut() -> Fut, + Fut: Future>, + { + loop { + match callback().await { + Err(RetryError::Retry(e)) if self.retries > 0 => { + self.handle_err(e); + if let Some(delay) = self.delay { + tokio::time::sleep(delay).await; + } + } + Err(RetryError::Retry(e) | RetryError::Break(e)) => return Err(e), + Ok(t) => return Ok(t), + }; + } + } + fn handle_err(&mut self, err: Error) { self.retries -= 1; warn!("erroneous attempt ({} tries remaining): {}", self.retries, err.root_cause()); diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index a051db1f0..c22ee3076 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -4,6 +4,7 @@ use crate::abi::abi_decode_calldata; use alloy_json_abi::JsonAbi; +use eyre::Context; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ @@ -39,14 +40,15 @@ pub struct OpenChainClient { impl OpenChainClient { /// Creates a new client with default settings - pub fn new() -> reqwest::Result { + pub fn new() -> eyre::Result { let inner = reqwest::Client::builder() .default_headers(HeaderMap::from_iter([( HeaderName::from_static("user-agent"), HeaderValue::from_static("forge"), )])) .timeout(REQ_TIMEOUT) - .build()?; + .build() + .wrap_err("failed to build OpenChain client")?; Ok(Self { inner, spurious_connection: Arc::new(Default::default()), @@ -61,16 +63,10 @@ impl OpenChainClient { .get(url) .send() .await - .map_err(|err| { - self.on_reqwest_err(&err); - err - })? + .inspect_err(|err| self.on_reqwest_err(err))? .text() .await - .map_err(|err| { - self.on_reqwest_err(&err); - err - }) + .inspect_err(|err| self.on_reqwest_err(err)) } /// Sends a new post request @@ -85,16 +81,10 @@ impl OpenChainClient { .json(body) .send() .await - .map_err(|err| { - self.on_reqwest_err(&err); - err - })? + .inspect_err(|err| self.on_reqwest_err(err))? .json() .await - .map_err(|err| { - self.on_reqwest_err(&err); - err - }) + .inspect_err(|err| self.on_reqwest_err(err)) } fn on_reqwest_err(&self, err: &reqwest::Error) { @@ -529,7 +519,7 @@ pub async fn import_selectors(data: SelectorImportData) -> eyre::Result, diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index 64d27563e..2b41a5e58 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -3,7 +3,7 @@ use alloy_json_abi::Function; use alloy_primitives::Bytes; use alloy_sol_types::SolError; -use std::path::Path; +use std::{fmt, path::Path}; /// Test filter. pub trait TestFilter: Send + Sync { @@ -19,116 +19,216 @@ pub trait TestFilter: Send + Sync { /// Extension trait for `Function`. pub trait TestFunctionExt { - /// Returns whether this function should be executed as invariant test. - fn is_invariant_test(&self) -> bool; + /// Returns the kind of test function. + fn test_function_kind(&self) -> TestFunctionKind { + TestFunctionKind::classify(self.tfe_as_str(), self.tfe_has_inputs()) + } + + /// Returns `true` if this function is a `setUp` function. + fn is_setup(&self) -> bool { + self.test_function_kind().is_setup() + } + + /// Returns `true` if this function is a unit, fuzz, or invariant test. + fn is_any_test(&self) -> bool { + self.test_function_kind().is_any_test() + } - /// Returns whether this function should be executed as fuzz test. - fn is_fuzz_test(&self) -> bool; + /// Returns `true` if this function is a test that should fail. + fn is_any_test_fail(&self) -> bool { + self.test_function_kind().is_any_test_fail() + } + + /// Returns `true` if this function is a unit test. + fn is_unit_test(&self) -> bool { + matches!(self.test_function_kind(), TestFunctionKind::UnitTest { .. }) + } - /// Returns whether this function is a test. - fn is_test(&self) -> bool; + /// Returns `true` if this function is a fuzz test. + fn is_fuzz_test(&self) -> bool { + self.test_function_kind().is_fuzz_test() + } - /// Returns whether this function is a test that should fail. - fn is_test_fail(&self) -> bool; + /// Returns `true` if this function is an invariant test. + fn is_invariant_test(&self) -> bool { + self.test_function_kind().is_invariant_test() + } - /// Returns whether this function is a `setUp` function. - fn is_setup(&self) -> bool; + /// Returns `true` if this function is an `afterInvariant` function. + fn is_after_invariant(&self) -> bool { + self.test_function_kind().is_after_invariant() + } - /// Returns whether this function is `afterInvariant` function. - fn is_after_invariant(&self) -> bool; + /// Returns `true` if this function is a `fixture` function. + fn is_fixture(&self) -> bool { + self.test_function_kind().is_fixture() + } - /// Returns whether this function is a fixture function. - fn is_fixture(&self) -> bool; + #[doc(hidden)] + fn tfe_as_str(&self) -> &str; + #[doc(hidden)] + fn tfe_has_inputs(&self) -> bool; } impl TestFunctionExt for Function { - fn is_invariant_test(&self) -> bool { - self.name.is_invariant_test() + fn tfe_as_str(&self) -> &str { + self.name.as_str() } - fn is_fuzz_test(&self) -> bool { - // test functions that have inputs are considered fuzz tests as those inputs will be fuzzed + fn tfe_has_inputs(&self) -> bool { !self.inputs.is_empty() } +} - fn is_test(&self) -> bool { - self.name.is_test() - } - - fn is_test_fail(&self) -> bool { - self.name.is_test_fail() +impl TestFunctionExt for String { + fn tfe_as_str(&self) -> &str { + self } - fn is_setup(&self) -> bool { - self.name.is_setup() + fn tfe_has_inputs(&self) -> bool { + false } fn is_after_invariant(&self) -> bool { - self.name.is_after_invariant() + self.as_str().is_after_invariant() } fn is_fixture(&self) -> bool { - self.name.is_fixture() + self.as_str().is_fixture() } } -impl TestFunctionExt for String { - fn is_invariant_test(&self) -> bool { - self.as_str().is_invariant_test() +impl TestFunctionExt for str { + fn tfe_as_str(&self) -> &str { + self } - fn is_fuzz_test(&self) -> bool { - self.as_str().is_fuzz_test() + fn tfe_has_inputs(&self) -> bool { + false } +} + +/// Test function kind. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum TestFunctionKind { + /// `setUp`. + Setup, + /// `test*`. `should_fail` is `true` for `testFail*`. + UnitTest { should_fail: bool }, + /// `test*`, with arguments. `should_fail` is `true` for `testFail*`. + FuzzTest { should_fail: bool }, + /// `invariant*` or `statefulFuzz*`. + InvariantTest, + /// `afterInvariant`. + AfterInvariant, + /// `fixture*`. + Fixture, + /// Unknown kind. + Unknown, +} - fn is_test(&self) -> bool { - self.as_str().is_test() +impl TestFunctionKind { + /// Classify a function. + #[inline] + pub fn classify(name: &str, has_inputs: bool) -> Self { + match () { + _ if name.starts_with("test") => { + let should_fail = name.starts_with("testFail"); + if has_inputs { + Self::FuzzTest { should_fail } + } else { + Self::UnitTest { should_fail } + } + } + _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => { + Self::InvariantTest + } + _ if name.eq_ignore_ascii_case("setup") => Self::Setup, + _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant, + _ if name.starts_with("fixture") => Self::Fixture, + _ => Self::Unknown, + } } - fn is_test_fail(&self) -> bool { - self.as_str().is_test_fail() + /// Returns the name of the function kind. + pub const fn name(&self) -> &'static str { + match self { + Self::Setup => "setUp", + Self::UnitTest { should_fail: false } => "test", + Self::UnitTest { should_fail: true } => "testFail", + Self::FuzzTest { should_fail: false } => "fuzz", + Self::FuzzTest { should_fail: true } => "fuzz fail", + Self::InvariantTest => "invariant", + Self::AfterInvariant => "afterInvariant", + Self::Fixture => "fixture", + Self::Unknown => "unknown", + } } - fn is_setup(&self) -> bool { - self.as_str().is_setup() + /// Returns `true` if this function is a `setUp` function. + #[inline] + pub const fn is_setup(&self) -> bool { + matches!(self, Self::Setup) } - fn is_after_invariant(&self) -> bool { - self.as_str().is_after_invariant() + /// Returns `true` if this function is a unit, fuzz, or invariant test. + #[inline] + pub const fn is_any_test(&self) -> bool { + matches!(self, Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::InvariantTest) } - fn is_fixture(&self) -> bool { - self.as_str().is_fixture() + /// Returns `true` if this function is a test that should fail. + #[inline] + pub const fn is_any_test_fail(&self) -> bool { + matches!(self, Self::UnitTest { should_fail: true } | Self::FuzzTest { should_fail: true }) } -} -impl TestFunctionExt for str { - fn is_invariant_test(&self) -> bool { - self.starts_with("invariant") || self.starts_with("statefulFuzz") + /// Returns `true` if this function is a unit test. + #[inline] + pub fn is_unit_test(&self) -> bool { + matches!(self, Self::UnitTest { .. }) } - fn is_fuzz_test(&self) -> bool { - unimplemented!("no naming convention for fuzz tests") + /// Returns `true` if this function is a fuzz test. + #[inline] + pub const fn is_fuzz_test(&self) -> bool { + matches!(self, Self::FuzzTest { .. }) } - fn is_test(&self) -> bool { - self.starts_with("test") + /// Returns `true` if this function is an invariant test. + #[inline] + pub const fn is_invariant_test(&self) -> bool { + matches!(self, Self::InvariantTest) } - fn is_test_fail(&self) -> bool { - self.starts_with("testFail") + /// Returns `true` if this function is an `afterInvariant` function. + #[inline] + pub const fn is_after_invariant(&self) -> bool { + matches!(self, Self::AfterInvariant) } - fn is_setup(&self) -> bool { - self.eq_ignore_ascii_case("setup") + /// Returns `true` if this function is a `fixture` function. + #[inline] + pub const fn is_fixture(&self) -> bool { + matches!(self, Self::Fixture) } - fn is_after_invariant(&self) -> bool { - self.eq_ignore_ascii_case("afterinvariant") + /// Returns `true` if this function kind is known. + #[inline] + pub const fn is_known(&self) -> bool { + !matches!(self, Self::Unknown) } - fn is_fixture(&self) -> bool { - self.starts_with("fixture") + /// Returns `true` if this function kind is unknown. + #[inline] + pub const fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown) + } +} + +impl fmt::Display for TestFunctionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.name().fmt(f) } } diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 9a6ba190e..2693c8ac2 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -5,6 +5,7 @@ use alloy_rpc_types::{AnyTransactionReceipt, BlockId}; use alloy_serde::WithOtherFields; use alloy_transport::Transport; use eyre::Result; +use foundry_common_fmt::UIfmt; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason @@ -75,6 +76,21 @@ impl From for AnyTransactionReceipt { } } +impl UIfmt for TransactionReceiptWithRevertReason { + fn pretty(&self) -> String { + if let Some(revert_reason) = &self.revert_reason { + format!( + "{} +revertReason {}", + self.receipt.pretty(), + revert_reason + ) + } else { + self.receipt.pretty() + } + } +} + fn extract_revert_reason>(error_string: S) -> Option { let message_substr = "execution reverted: "; error_string @@ -83,6 +99,38 @@ fn extract_revert_reason>(error_string: S) -> Option { .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) } +/// Returns the `UiFmt::pretty()` formatted attribute of the transaction receipt +pub fn get_pretty_tx_receipt_attr( + receipt: &TransactionReceiptWithRevertReason, + attr: &str, +) -> Option { + match attr { + "blockHash" | "block_hash" => Some(receipt.receipt.block_hash.pretty()), + "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), + "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), + "cumulativeGasUsed" | "cumulative_gas_used" => { + Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) + } + "effectiveGasPrice" | "effective_gas_price" => { + Some(receipt.receipt.effective_gas_price.to_string()) + } + "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), + "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), + "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), + "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root.pretty()), + "status" | "statusCode" | "status_code" => { + Some(receipt.receipt.inner.inner.inner.receipt.status.pretty()) + } + "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), + "transactionIndex" | "transaction_index" => { + Some(receipt.receipt.transaction_index.pretty()) + } + "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), + "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), + _ => None, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 52f27d40d..9273744dd 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-compilers = { workspace = true, features = ["svm-solc", "async"] } @@ -52,3 +55,4 @@ tempfile.workspace = true [features] default = ["rustls"] rustls = ["reqwest/rustls-tls-native-roots"] +isolate-by-default = [] diff --git a/crates/config/README.md b/crates/config/README.md index 374bcdd39..624452057 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -114,6 +114,11 @@ match_contract = "Foo" no_match_contract = "Bar" match_path = "*/Foo*" no_match_path = "*/Bar*" +no_match_coverage = "Baz" +# Number of threads to use. Not set or zero specifies the number of logical cores. +threads = 0 +# whether to show test execution progress +show_progress = true ffi = false always_use_create_2_factory = false prompt_timeout = 120 @@ -124,9 +129,10 @@ initial_balance = '0xffffffffffffffffffffffff' block_number = 0 fork_block_number = 0 chain_id = 1 -# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds `i64::MAX` (9223372036854775807) -# `gas_limit = "Max"` is equivalent to `gas_limit = "18446744073709551615"` -gas_limit = 9223372036854775807 +# NOTE due to a toml-rs limitation, this value needs to be a string if the desired gas limit exceeds 2**63-1 (9223372036854775807). +# `gas_limit = "max"` is equivalent to `gas_limit = "18446744073709551615"`. This is not recommended +# as it will make infinite loops effectively hang during execution. +gas_limit = 1073741824 gas_price = 0 block_base_fee_per_gas = 0 block_coinbase = '0x0000000000000000000000000000000000000000' @@ -179,6 +185,11 @@ root = "root" # following example enables read-write access for the project dir : # `fs_permissions = [{ access = "read-write", path = "./"}]` fs_permissions = [{ access = "read", path = "./out"}] +# whether failed assertions should revert +# note that this only applies to native (cheatcode) assertions, invoked on Vm contract +assertions_revert = true +# whether `failed()` should be invoked to check if the test have failed +legacy_assertions = false [fuzz] runs = 256 max_test_rejects = 65536 diff --git a/crates/config/src/bind_json.rs b/crates/config/src/bind_json.rs new file mode 100644 index 000000000..71d8d41aa --- /dev/null +++ b/crates/config/src/bind_json.rs @@ -0,0 +1,27 @@ +use crate::filter::GlobMatcher; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// Contains the config for `forge bind-json` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct BindJsonConfig { + /// Path for the generated bindings file. + pub out: PathBuf, + /// Globs to include. + /// + /// If provided, only the files matching the globs will be included. Otherwise, defaults to + /// including all project files. + pub include: Vec, + /// Globs to ignore + pub exclude: Vec, +} + +impl Default for BindJsonConfig { + fn default() -> Self { + Self { + out: PathBuf::from("utils/JsonBindings.sol"), + exclude: Vec::new(), + include: Vec::new(), + } + } +} diff --git a/crates/config/src/cache.rs b/crates/config/src/cache.rs index 58b3b8cbe..d087b5e6a 100644 --- a/crates/config/src/cache.rs +++ b/crates/config/src/cache.rs @@ -45,9 +45,9 @@ impl CachedChains { /// Whether the `endpoint` matches pub fn is_match(&self, chain: u64) -> bool { match self { - CachedChains::All => true, - CachedChains::None => false, - CachedChains::Chains(chains) => chains.iter().any(|c| c.id() == chain), + Self::All => true, + Self::None => false, + Self::Chains(chains) => chains.iter().any(|c| c.id() == chain), } } } @@ -58,9 +58,9 @@ impl Serialize for CachedChains { S: Serializer, { match self { - CachedChains::All => serializer.serialize_str("all"), - CachedChains::None => serializer.serialize_str("none"), - CachedChains::Chains(chains) => chains.serialize(serializer), + Self::All => serializer.serialize_str("all"), + Self::None => serializer.serialize_str("none"), + Self::Chains(chains) => chains.serialize(serializer), } } } @@ -79,11 +79,11 @@ impl<'de> Deserialize<'de> for CachedChains { match Chains::deserialize(deserializer)? { Chains::All(s) => match s.as_str() { - "all" => Ok(CachedChains::All), - "none" => Ok(CachedChains::None), + "all" => Ok(Self::All), + "none" => Ok(Self::None), s => Err(serde::de::Error::unknown_variant(s, &["all", "none"])), }, - Chains::Chains(chains) => Ok(CachedChains::Chains(chains)), + Chains::Chains(chains) => Ok(Self::Chains(chains)), } } } @@ -105,11 +105,9 @@ impl CachedEndpoints { pub fn is_match(&self, endpoint: impl AsRef) -> bool { let endpoint = endpoint.as_ref(); match self { - CachedEndpoints::All => true, - CachedEndpoints::Remote => { - !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:") - } - CachedEndpoints::Pattern(re) => re.is_match(endpoint), + Self::All => true, + Self::Remote => !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:"), + Self::Pattern(re) => re.is_match(endpoint), } } } @@ -117,9 +115,9 @@ impl CachedEndpoints { impl PartialEq for CachedEndpoints { fn eq(&self, other: &Self) -> bool { match (self, other) { - (CachedEndpoints::Pattern(a), CachedEndpoints::Pattern(b)) => a.as_str() == b.as_str(), - (&CachedEndpoints::All, &CachedEndpoints::All) => true, - (&CachedEndpoints::Remote, &CachedEndpoints::Remote) => true, + (Self::Pattern(a), Self::Pattern(b)) => a.as_str() == b.as_str(), + (&Self::All, &Self::All) => true, + (&Self::Remote, &Self::Remote) => true, _ => false, } } @@ -130,9 +128,9 @@ impl Eq for CachedEndpoints {} impl fmt::Display for CachedEndpoints { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CachedEndpoints::All => f.write_str("all"), - CachedEndpoints::Remote => f.write_str("remote"), - CachedEndpoints::Pattern(s) => s.fmt(f), + Self::All => f.write_str("all"), + Self::Remote => f.write_str("remote"), + Self::Pattern(s) => s.fmt(f), } } } @@ -142,9 +140,9 @@ impl FromStr for CachedEndpoints { fn from_str(s: &str) -> Result { match s { - "all" => Ok(CachedEndpoints::All), - "remote" => Ok(CachedEndpoints::Remote), - _ => Ok(CachedEndpoints::Pattern(s.parse()?)), + "all" => Ok(Self::All), + "remote" => Ok(Self::Remote), + _ => Ok(Self::Pattern(s.parse()?)), } } } @@ -164,9 +162,9 @@ impl Serialize for CachedEndpoints { S: Serializer, { match self { - CachedEndpoints::All => serializer.serialize_str("all"), - CachedEndpoints::Remote => serializer.serialize_str("remote"), - CachedEndpoints::Pattern(pattern) => serializer.serialize_str(pattern.as_str()), + Self::All => serializer.serialize_str("all"), + Self::Remote => serializer.serialize_str("remote"), + Self::Pattern(pattern) => serializer.serialize_str(pattern.as_str()), } } } diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 74157b0e9..eabc5acb1 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -68,16 +68,16 @@ impl RpcEndpointType { /// Returns the string variant pub fn as_endpoint_string(&self) -> Option<&RpcEndpoint> { match self { - RpcEndpointType::String(url) => Some(url), - RpcEndpointType::Config(_) => None, + Self::String(url) => Some(url), + Self::Config(_) => None, } } /// Returns the config variant pub fn as_endpoint_config(&self) -> Option<&RpcEndpointConfig> { match self { - RpcEndpointType::Config(config) => Some(config), - RpcEndpointType::String(_) => None, + Self::Config(config) => Some(config), + Self::String(_) => None, } } @@ -88,8 +88,8 @@ impl RpcEndpointType { /// Returns an error if the type holds a reference to an env var and the env var is not set pub fn resolve(self) -> Result { match self { - RpcEndpointType::String(url) => url.resolve(), - RpcEndpointType::Config(config) => config.endpoint.resolve(), + Self::String(url) => url.resolve(), + Self::Config(config) => config.endpoint.resolve(), } } } @@ -97,8 +97,8 @@ impl RpcEndpointType { impl fmt::Display for RpcEndpointType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - RpcEndpointType::String(url) => url.fmt(f), - RpcEndpointType::Config(config) => config.fmt(f), + Self::String(url) => url.fmt(f), + Self::Config(config) => config.fmt(f), } } } @@ -134,16 +134,16 @@ impl RpcEndpoint { /// Returns the url variant pub fn as_url(&self) -> Option<&str> { match self { - RpcEndpoint::Url(url) => Some(url), - RpcEndpoint::Env(_) => None, + Self::Url(url) => Some(url), + Self::Env(_) => None, } } /// Returns the env variant pub fn as_env(&self) -> Option<&str> { match self { - RpcEndpoint::Env(val) => Some(val), - RpcEndpoint::Url(_) => None, + Self::Env(val) => Some(val), + Self::Url(_) => None, } } @@ -154,8 +154,8 @@ impl RpcEndpoint { /// Returns an error if the type holds a reference to an env var and the env var is not set pub fn resolve(self) -> Result { match self { - RpcEndpoint::Url(url) => Ok(url), - RpcEndpoint::Env(val) => interpolate(&val), + Self::Url(url) => Ok(url), + Self::Env(val) => interpolate(&val), } } } @@ -163,8 +163,8 @@ impl RpcEndpoint { impl fmt::Display for RpcEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - RpcEndpoint::Url(url) => url.fmt(f), - RpcEndpoint::Env(var) => var.fmt(f), + Self::Url(url) => url.fmt(f), + Self::Env(var) => var.fmt(f), } } } @@ -192,11 +192,7 @@ impl<'de> Deserialize<'de> for RpcEndpoint { D: Deserializer<'de>, { let val = String::deserialize(deserializer)?; - let endpoint = if RE_PLACEHOLDER.is_match(&val) { - RpcEndpoint::Env(val) - } else { - RpcEndpoint::Url(val) - }; + let endpoint = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Url(val) }; Ok(endpoint) } @@ -204,13 +200,13 @@ impl<'de> Deserialize<'de> for RpcEndpoint { impl From for RpcEndpointType { fn from(endpoint: RpcEndpoint) -> Self { - RpcEndpointType::String(endpoint) + Self::String(endpoint) } } impl From for RpcEndpointConfig { fn from(endpoint: RpcEndpoint) -> Self { - RpcEndpointConfig { endpoint, ..Default::default() } + Self { endpoint, ..Default::default() } } } @@ -241,20 +237,20 @@ impl RpcEndpointConfig { impl fmt::Display for RpcEndpointConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let RpcEndpointConfig { endpoint, retries, retry_backoff, compute_units_per_second } = self; + let Self { endpoint, retries, retry_backoff, compute_units_per_second } = self; - write!(f, "{}", endpoint)?; + write!(f, "{endpoint}")?; if let Some(retries) = retries { - write!(f, ", retries={}", retries)?; + write!(f, ", retries={retries}")?; } if let Some(retry_backoff) = retry_backoff { - write!(f, ", retry_backoff={}", retry_backoff)?; + write!(f, ", retry_backoff={retry_backoff}")?; } if let Some(compute_units_per_second) = compute_units_per_second { - write!(f, ", compute_units_per_second={}", compute_units_per_second)?; + write!(f, ", compute_units_per_second={compute_units_per_second}")?; } Ok(()) @@ -308,13 +304,13 @@ impl<'de> Deserialize<'de> for RpcEndpointConfig { let RpcEndpointConfigInner { endpoint, retries, retry_backoff, compute_units_per_second } = serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Ok(RpcEndpointConfig { endpoint, retries, retry_backoff, compute_units_per_second }) + Ok(Self { endpoint, retries, retry_backoff, compute_units_per_second }) } } impl From for RpcEndpointType { fn from(config: RpcEndpointConfig) -> Self { - RpcEndpointType::Config(config) + Self::Config(config) } } diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 016e32c47..3da1aee09 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -75,11 +75,11 @@ impl fmt::Display for FoundryConfigError { }; match self { - FoundryConfigError::Toml(err) => { + Self::Toml(err) => { f.write_str("foundry.toml error: ")?; fmt_err(err, f) } - FoundryConfigError::Other(err) => { + Self::Other(err) => { f.write_str("foundry config error: ")?; fmt_err(err, f) } @@ -90,9 +90,7 @@ impl fmt::Display for FoundryConfigError { impl Error for FoundryConfigError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { - FoundryConfigError::Other(error) | FoundryConfigError::Toml(error) => { - Error::source(error) - } + Self::Other(error) | Self::Toml(error) => Error::source(error), } } } @@ -148,31 +146,31 @@ impl SolidityErrorCode { /// Returns `Err(code)` if unknown error pub fn as_str(&self) -> Result<&'static str, u64> { let s = match self { - SolidityErrorCode::SpdxLicenseNotProvided => "license", - SolidityErrorCode::VisibilityForConstructorIsIgnored => "constructor-visibility", - SolidityErrorCode::ContractExceeds24576Bytes => "code-size", - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => "init-code-size", - SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => "func-mutability", - SolidityErrorCode::UnusedLocalVariable => "unused-var", - SolidityErrorCode::UnusedFunctionParameter => "unused-param", - SolidityErrorCode::ReturnValueOfCallsNotUsed => "unused-return", - SolidityErrorCode::InterfacesExplicitlyVirtual => "virtual-interfaces", - SolidityErrorCode::PayableNoReceiveEther => "missing-receive-ether", - SolidityErrorCode::ShadowsExistingDeclaration => "shadowing", - SolidityErrorCode::DeclarationSameNameAsAnother => "same-varname", - SolidityErrorCode::UnnamedReturnVariable => "unnamed-return", - SolidityErrorCode::Unreachable => "unreachable", - SolidityErrorCode::PragmaSolidity => "pragma-solidity", - SolidityErrorCode::TransientStorageUsed => "transient-storage", - SolidityErrorCode::TooManyWarnings => "too-many-warnings", - SolidityErrorCode::Other(code) => return Err(*code), + Self::SpdxLicenseNotProvided => "license", + Self::VisibilityForConstructorIsIgnored => "constructor-visibility", + Self::ContractExceeds24576Bytes => "code-size", + Self::ContractInitCodeSizeExceeds49152Bytes => "init-code-size", + Self::FunctionStateMutabilityCanBeRestricted => "func-mutability", + Self::UnusedLocalVariable => "unused-var", + Self::UnusedFunctionParameter => "unused-param", + Self::ReturnValueOfCallsNotUsed => "unused-return", + Self::InterfacesExplicitlyVirtual => "virtual-interfaces", + Self::PayableNoReceiveEther => "missing-receive-ether", + Self::ShadowsExistingDeclaration => "shadowing", + Self::DeclarationSameNameAsAnother => "same-varname", + Self::UnnamedReturnVariable => "unnamed-return", + Self::Unreachable => "unreachable", + Self::PragmaSolidity => "pragma-solidity", + Self::TransientStorageUsed => "transient-storage", + Self::TooManyWarnings => "too-many-warnings", + Self::Other(code) => return Err(*code), }; Ok(s) } } impl From for u64 { - fn from(code: SolidityErrorCode) -> u64 { + fn from(code: SolidityErrorCode) -> Self { match code { SolidityErrorCode::SpdxLicenseNotProvided => 1878, SolidityErrorCode::VisibilityForConstructorIsIgnored => 2462, @@ -210,23 +208,23 @@ impl FromStr for SolidityErrorCode { fn from_str(s: &str) -> Result { let code = match s { - "license" => SolidityErrorCode::SpdxLicenseNotProvided, - "constructor-visibility" => SolidityErrorCode::VisibilityForConstructorIsIgnored, - "code-size" => SolidityErrorCode::ContractExceeds24576Bytes, - "init-code-size" => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - "func-mutability" => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - "unused-var" => SolidityErrorCode::UnusedLocalVariable, - "unused-param" => SolidityErrorCode::UnusedFunctionParameter, - "unused-return" => SolidityErrorCode::ReturnValueOfCallsNotUsed, - "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, - "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, - "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, - "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, - "unnamed-return" => SolidityErrorCode::UnnamedReturnVariable, - "unreachable" => SolidityErrorCode::Unreachable, - "pragma-solidity" => SolidityErrorCode::PragmaSolidity, - "transient-storage" => SolidityErrorCode::TransientStorageUsed, - "too-many-warnings" => SolidityErrorCode::TooManyWarnings, + "license" => Self::SpdxLicenseNotProvided, + "constructor-visibility" => Self::VisibilityForConstructorIsIgnored, + "code-size" => Self::ContractExceeds24576Bytes, + "init-code-size" => Self::ContractInitCodeSizeExceeds49152Bytes, + "func-mutability" => Self::FunctionStateMutabilityCanBeRestricted, + "unused-var" => Self::UnusedLocalVariable, + "unused-param" => Self::UnusedFunctionParameter, + "unused-return" => Self::ReturnValueOfCallsNotUsed, + "virtual-interfaces" => Self::InterfacesExplicitlyVirtual, + "missing-receive-ether" => Self::PayableNoReceiveEther, + "shadowing" => Self::ShadowsExistingDeclaration, + "same-varname" => Self::DeclarationSameNameAsAnother, + "unnamed-return" => Self::UnnamedReturnVariable, + "unreachable" => Self::Unreachable, + "pragma-solidity" => Self::PragmaSolidity, + "transient-storage" => Self::TransientStorageUsed, + "too-many-warnings" => Self::TooManyWarnings, _ => return Err(format!("Unknown variant {s}")), }; @@ -237,23 +235,23 @@ impl FromStr for SolidityErrorCode { impl From for SolidityErrorCode { fn from(code: u64) -> Self { match code { - 1878 => SolidityErrorCode::SpdxLicenseNotProvided, - 2462 => SolidityErrorCode::VisibilityForConstructorIsIgnored, - 5574 => SolidityErrorCode::ContractExceeds24576Bytes, - 3860 => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - 2018 => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - 2072 => SolidityErrorCode::UnusedLocalVariable, - 5667 => SolidityErrorCode::UnusedFunctionParameter, - 9302 => SolidityErrorCode::ReturnValueOfCallsNotUsed, - 5815 => SolidityErrorCode::InterfacesExplicitlyVirtual, - 3628 => SolidityErrorCode::PayableNoReceiveEther, - 2519 => SolidityErrorCode::ShadowsExistingDeclaration, - 8760 => SolidityErrorCode::DeclarationSameNameAsAnother, - 6321 => SolidityErrorCode::UnnamedReturnVariable, - 5740 => SolidityErrorCode::Unreachable, - 3420 => SolidityErrorCode::PragmaSolidity, - 2394 => SolidityErrorCode::TransientStorageUsed, - other => SolidityErrorCode::Other(other), + 1878 => Self::SpdxLicenseNotProvided, + 2462 => Self::VisibilityForConstructorIsIgnored, + 5574 => Self::ContractExceeds24576Bytes, + 3860 => Self::ContractInitCodeSizeExceeds49152Bytes, + 2018 => Self::FunctionStateMutabilityCanBeRestricted, + 2072 => Self::UnusedLocalVariable, + 5667 => Self::UnusedFunctionParameter, + 9302 => Self::ReturnValueOfCallsNotUsed, + 5815 => Self::InterfacesExplicitlyVirtual, + 3628 => Self::PayableNoReceiveEther, + 2519 => Self::ShadowsExistingDeclaration, + 8760 => Self::DeclarationSameNameAsAnother, + 6321 => Self::UnnamedReturnVariable, + 5740 => Self::Unreachable, + 3420 => Self::PragmaSolidity, + 2394 => Self::TransientStorageUsed, + other => Self::Other(other), } } } diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index c4f3fe700..9dde4b733 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -188,7 +188,7 @@ impl EtherscanConfig { self, alias: Option<&str>, ) -> Result { - let EtherscanConfig { chain, mut url, key } = self; + let Self { chain, mut url, key } = self; if let Some(url) = &mut url { *url = interpolate(url)?; @@ -294,7 +294,7 @@ impl ResolvedEtherscanConfig { self, ) -> Result { - let ResolvedEtherscanConfig { api_url, browser_url, key: api_key, chain } = self; + let Self { api_url, browser_url, key: api_key, chain } = self; let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed"); let cache = chain @@ -346,16 +346,16 @@ impl EtherscanApiKey { /// Returns the key variant pub fn as_key(&self) -> Option<&str> { match self { - EtherscanApiKey::Key(url) => Some(url), - EtherscanApiKey::Env(_) => None, + Self::Key(url) => Some(url), + Self::Env(_) => None, } } /// Returns the env variant pub fn as_env(&self) -> Option<&str> { match self { - EtherscanApiKey::Env(val) => Some(val), - EtherscanApiKey::Key(_) => None, + Self::Env(val) => Some(val), + Self::Key(_) => None, } } @@ -366,8 +366,8 @@ impl EtherscanApiKey { /// Returns an error if the type holds a reference to an env var and the env var is not set pub fn resolve(self) -> Result { match self { - EtherscanApiKey::Key(key) => Ok(key), - EtherscanApiKey::Env(val) => interpolate(&val), + Self::Key(key) => Ok(key), + Self::Env(val) => interpolate(&val), } } } @@ -387,11 +387,7 @@ impl<'de> Deserialize<'de> for EtherscanApiKey { D: Deserializer<'de>, { let val = String::deserialize(deserializer)?; - let endpoint = if RE_PLACEHOLDER.is_match(&val) { - EtherscanApiKey::Env(val) - } else { - EtherscanApiKey::Key(val) - }; + let endpoint = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Key(val) }; Ok(endpoint) } @@ -400,8 +396,8 @@ impl<'de> Deserialize<'de> for EtherscanApiKey { impl fmt::Display for EtherscanApiKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - EtherscanApiKey::Key(key) => key.fmt(f), - EtherscanApiKey::Env(var) => var.fmt(f), + Self::Key(key) => key.fmt(f), + Self::Env(var) => var.fmt(f), } } } diff --git a/crates/config/src/filter.rs b/crates/config/src/filter.rs index 385b44225..96b34fb03 100644 --- a/crates/config/src/filter.rs +++ b/crates/config/src/filter.rs @@ -2,6 +2,7 @@ use core::fmt; use foundry_compilers::FileFilter; +use serde::{Deserialize, Serialize}; use std::{ convert::Infallible, path::{Path, PathBuf}, @@ -55,6 +56,14 @@ impl GlobMatcher { return self.matcher.is_match(format!("./{}", path.display())); } + if path.is_relative() && Path::new(self.glob().glob()).is_absolute() { + if let Ok(canonicalized_path) = dunce::canonicalize(path) { + return self.matcher.is_match(canonicalized_path); + } else { + return false; + } + } + false } @@ -96,6 +105,27 @@ impl From for GlobMatcher { } } +impl Serialize for GlobMatcher { + fn serialize(&self, serializer: S) -> Result { + self.glob().glob().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for GlobMatcher { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} + +impl PartialEq for GlobMatcher { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for GlobMatcher {} + /// Bundles multiple `SkipBuildFilter` into a single `FileFilter` #[derive(Clone, Debug)] pub struct SkipBuildFilters { @@ -144,18 +174,18 @@ pub enum SkipBuildFilter { impl SkipBuildFilter { fn new(s: &str) -> Self { match s { - "test" | "tests" => SkipBuildFilter::Tests, - "script" | "scripts" => SkipBuildFilter::Scripts, - s => SkipBuildFilter::Custom(s.to_string()), + "test" | "tests" => Self::Tests, + "script" | "scripts" => Self::Scripts, + s => Self::Custom(s.to_string()), } } /// Returns the pattern to match against a file pub fn file_pattern(&self) -> &str { match self { - SkipBuildFilter::Tests => ".t.sol", - SkipBuildFilter::Scripts => ".s.sol", - SkipBuildFilter::Custom(s) => s.as_str(), + Self::Tests => ".t.sol", + Self::Scripts => ".s.sol", + Self::Custom(s) => s.as_str(), } } } @@ -196,9 +226,27 @@ mod tests { } #[test] - fn can_match_glob_paths() { + fn can_match_relative_glob_paths() { let matcher: GlobMatcher = "./test/*".parse().unwrap(); - assert!(matcher.is_match(Path::new("test/Contract.sol"))); - assert!(matcher.is_match(Path::new("./test/Contract.sol"))); + + // Absolute path that should match the pattern + assert!(matcher.is_match(Path::new("test/Contract.t.sol"))); + + // Relative path that should match the pattern + assert!(matcher.is_match(Path::new("./test/Contract.t.sol"))); + } + + #[test] + fn can_match_absolute_glob_paths() { + let matcher: GlobMatcher = "/home/user/projects/project/test/*".parse().unwrap(); + + // Absolute path that should match the pattern + assert!(matcher.is_match(Path::new("/home/user/projects/project/test/Contract.t.sol"))); + + // Absolute path that should not match the pattern + assert!(!matcher.is_match(Path::new("/home/user/other/project/test/Contract.t.sol"))); + + // Relative path that should not match an absolute pattern + assert!(!matcher.is_match(Path::new("projects/project/test/Contract.t.sol"))); } } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index a1cc66c08..e1ebf7207 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -65,19 +65,19 @@ impl NumberUnderscore { /// Returns true if the option is `Preserve` #[inline] pub fn is_preserve(self) -> bool { - matches!(self, NumberUnderscore::Preserve) + matches!(self, Self::Preserve) } /// Returns true if the option is `Remove` #[inline] pub fn is_remove(self) -> bool { - matches!(self, NumberUnderscore::Remove) + matches!(self, Self::Remove) } /// Returns true if the option is `Remove` #[inline] pub fn is_thousands(self) -> bool { - matches!(self, NumberUnderscore::Thousands) + matches!(self, Self::Thousands) } } @@ -98,19 +98,19 @@ impl HexUnderscore { /// Returns true if the option is `Preserve` #[inline] pub fn is_preserve(self) -> bool { - matches!(self, HexUnderscore::Preserve) + matches!(self, Self::Preserve) } /// Returns true if the option is `Remove` #[inline] pub fn is_remove(self) -> bool { - matches!(self, HexUnderscore::Remove) + matches!(self, Self::Remove) } /// Returns true if the option is `Remove` #[inline] pub fn is_bytes(self) -> bool { - matches!(self, HexUnderscore::Bytes) + matches!(self, Self::Bytes) } } @@ -130,9 +130,9 @@ impl QuoteStyle { /// Get associated quotation mark with option pub fn quote(self) -> Option { match self { - QuoteStyle::Double => Some('"'), - QuoteStyle::Single => Some('\''), - QuoteStyle::Preserve => None, + Self::Double => Some('"'), + Self::Single => Some('\''), + Self::Preserve => None, } } } @@ -164,7 +164,7 @@ pub enum MultilineFuncHeaderStyle { impl Default for FormatterConfig { fn default() -> Self { - FormatterConfig { + Self { line_length: 120, tab_width: 4, bracket_spacing: false, diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index 5260b7488..1d2c35ff3 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -148,8 +148,8 @@ pub enum FsAccessKind { impl fmt::Display for FsAccessKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FsAccessKind::Read => f.write_str("read"), - FsAccessKind::Write => f.write_str("write"), + Self::Read => f.write_str("read"), + Self::Write => f.write_str("write"), } } } @@ -172,10 +172,10 @@ impl FsAccessPermission { /// Returns true if the access is allowed pub fn is_granted(&self, kind: FsAccessKind) -> bool { match (self, kind) { - (FsAccessPermission::ReadWrite, _) => true, - (FsAccessPermission::None, _) => false, - (FsAccessPermission::Read, FsAccessKind::Read) => true, - (FsAccessPermission::Write, FsAccessKind::Write) => true, + (Self::ReadWrite, _) => true, + (Self::None, _) => false, + (Self::Read, FsAccessKind::Read) => true, + (Self::Write, FsAccessKind::Write) => true, _ => false, } } @@ -186,10 +186,10 @@ impl FromStr for FsAccessPermission { fn from_str(s: &str) -> Result { match s { - "true" | "read-write" | "readwrite" => Ok(FsAccessPermission::ReadWrite), - "false" | "none" => Ok(FsAccessPermission::None), - "read" => Ok(FsAccessPermission::Read), - "write" => Ok(FsAccessPermission::Write), + "true" | "read-write" | "readwrite" => Ok(Self::ReadWrite), + "false" | "none" => Ok(Self::None), + "read" => Ok(Self::Read), + "write" => Ok(Self::Write), _ => Err(format!("Unknown variant {s}")), } } @@ -198,10 +198,10 @@ impl FromStr for FsAccessPermission { impl fmt::Display for FsAccessPermission { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FsAccessPermission::ReadWrite => f.write_str("read-write"), - FsAccessPermission::None => f.write_str("none"), - FsAccessPermission::Read => f.write_str("read"), - FsAccessPermission::Write => f.write_str("write"), + Self::ReadWrite => f.write_str("read-write"), + Self::None => f.write_str("none"), + Self::Read => f.write_str("read"), + Self::Write => f.write_str("write"), } } } @@ -212,10 +212,10 @@ impl Serialize for FsAccessPermission { S: Serializer, { match self { - FsAccessPermission::ReadWrite => serializer.serialize_bool(true), - FsAccessPermission::None => serializer.serialize_bool(false), - FsAccessPermission::Read => serializer.serialize_str("read"), - FsAccessPermission::Write => serializer.serialize_str("write"), + Self::ReadWrite => serializer.serialize_bool(true), + Self::None => serializer.serialize_bool(false), + Self::Read => serializer.serialize_str("read"), + Self::Write => serializer.serialize_str("write"), } } } @@ -233,8 +233,7 @@ impl<'de> Deserialize<'de> for FsAccessPermission { } match Status::deserialize(deserializer)? { Status::Bool(enabled) => { - let status = - if enabled { FsAccessPermission::ReadWrite } else { FsAccessPermission::None }; + let status = if enabled { Self::ReadWrite } else { Self::None }; Ok(status) } Status::String(val) => val.parse().map_err(serde::de::Error::custom), diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 601b799b6..94410c21e 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -32,11 +32,13 @@ pub struct FuzzConfig { pub failure_persist_file: Option, /// When enabled, filters all addresses below 2^16, as they are reserved in zkSync. pub no_zksync_reserved_addresses: bool, + /// show `console.log` in fuzz test, defaults to `false` + pub show_logs: bool, } impl Default for FuzzConfig { fn default() -> Self { - FuzzConfig { + Self { runs: 256, max_test_rejects: 65536, seed: None, @@ -45,6 +47,7 @@ impl Default for FuzzConfig { failure_persist_dir: None, failure_persist_file: None, no_zksync_reserved_addresses: false, + show_logs: false, } } } @@ -52,7 +55,7 @@ impl Default for FuzzConfig { impl FuzzConfig { /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. pub fn new(cache_dir: PathBuf) -> Self { - FuzzConfig { + Self { runs: 256, max_test_rejects: 65536, seed: None, @@ -61,6 +64,7 @@ impl FuzzConfig { failure_persist_dir: Some(cache_dir), failure_persist_file: Some("failures".to_string()), no_zksync_reserved_addresses: false, + show_logs: false, } } } @@ -92,6 +96,7 @@ impl InlineConfigParser for FuzzConfig { "no-zksync-reserved-addresses" => { conf_clone.no_zksync_reserved_addresses = parse_config_bool(key, value)? } + "show-logs" => conf_clone.show_logs = parse_config_bool(key, value)?, _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, } } @@ -123,7 +128,7 @@ pub struct FuzzDictionaryConfig { impl Default for FuzzDictionaryConfig { fn default() -> Self { - FuzzDictionaryConfig { + Self { dictionary_weight: 40, include_storage: true, include_push_bytes: true, diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs index 1f6fca6c7..c2b7b39f7 100644 --- a/crates/config/src/inline/conf_parser.rs +++ b/crates/config/src/inline/conf_parser.rs @@ -37,29 +37,17 @@ where /// - `Err(InlineConfigParserError)` in case of wrong configuration. fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError>; - /// Validates all configurations contained in a natspec that apply - /// to the current configuration key. - /// - /// i.e. Given the `invariant` config key and a natspec comment of the form, - /// ```solidity - /// /// forge-config: default.invariant.runs = 500 - /// /// forge-config: default.invariant.depth = 500 - /// /// forge-config: ci.invariant.depth = 500 - /// /// forge-config: ci.fuzz.runs = 10 - /// ``` - /// would validate the whole `invariant` configuration. - fn validate_configs(natspec: &NatSpec) -> Result<(), InlineConfigError> { + /// Validates and merges the natspec configs into the current config. + fn merge(&self, natspec: &NatSpec) -> Result, InlineConfigError> { let config_key = Self::config_key(); let configs = natspec.config_lines().filter(|l| l.contains(&config_key)).collect::>(); - Self::default().try_merge(&configs).map_err(|e| { + self.try_merge(&configs).map_err(|e| { let line = natspec.debug_context(); InlineConfigError { line, source: e } - })?; - - Ok(()) + }) } /// Given a list of config lines, returns all available pairs (key, value) matching the current diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index adc67424f..9bdf1d5d0 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -25,9 +25,12 @@ static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { /// to create configs directly bound to a solidity test. #[derive(Clone, Debug, Default)] pub struct InlineConfig { + /// Contract-level configurations, used for functions that do not have a specific + /// configuration. + contract_level: HashMap, /// Maps a (test-contract, test-function) pair /// to a specific configuration provided by the user. - configs: HashMap<(String, String), T>, + fn_level: HashMap<(String, String), T>, } impl InlineConfig { @@ -35,18 +38,22 @@ impl InlineConfig { /// Configuration is identified by the pair "contract", "function". pub fn get(&self, contract_id: &str, fn_name: &str) -> Option<&T> { let key = (contract_id.to_string(), fn_name.to_string()); - self.configs.get(&key) + self.fn_level.get(&key).or_else(|| self.contract_level.get(contract_id)) + } + + pub fn insert_contract(&mut self, contract_id: impl Into, config: T) { + self.contract_level.insert(contract_id.into(), config); } /// Inserts an inline configuration, for a test function. /// Configuration is identified by the pair "contract", "function". - pub fn insert(&mut self, contract_id: C, fn_name: F, config: T) + pub fn insert_fn(&mut self, contract_id: C, fn_name: F, config: T) where C: Into, F: Into, { let key = (contract_id.into(), fn_name.into()); - self.configs.insert(key, config); + self.fn_level.insert(key, config); } } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 27742eb56..6dd6b696c 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -4,7 +4,7 @@ use foundry_compilers::{ ProjectCompileOutput, }; use serde_json::Value; -use solang_parser::pt; +use solang_parser::{helpers::CodeLocation, pt}; use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations @@ -12,8 +12,8 @@ use std::{collections::BTreeMap, path::Path}; pub struct NatSpec { /// The parent contract of the natspec pub contract: String, - /// The function annotated with the natspec - pub function: String, + /// The function annotated with the natspec. None if the natspec is contract-level + pub function: Option, /// The line the natspec appears, in the form /// `row:col:length` i.e. `10:21:122` pub line: String, @@ -41,7 +41,7 @@ impl NatSpec { let mut used_solc_ast = false; if let Some(ast) = &artifact.ast { if let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { - solc.parse(&mut natspecs, &contract, node); + solc.parse(&mut natspecs, &contract, node, true); used_solc_ast = true; } } @@ -60,7 +60,7 @@ impl NatSpec { /// context, for debugging purposes 🐞 /// i.e. `test/Counter.t.sol:CounterTest:testFuzz_SetNumber` pub fn debug_context(&self) -> String { - format!("{}:{}", self.contract, self.function) + format!("{}:{}", self.contract, self.function.as_deref().unwrap_or_default()) } /// Returns a list of configuration lines that match the current profile @@ -95,7 +95,7 @@ impl SolcParser { /// the provided contract_id. fn contract_root_node<'a>(&self, nodes: &'a [Node], contract_id: &str) -> Option<&'a Node> { for n in nodes.iter() { - if let NodeType::ContractDefinition = n.node_type { + if n.node_type == NodeType::ContractDefinition { let contract_data = &n.other; if let Value::String(contract_name) = contract_data.get("name")? { if contract_id.ends_with(contract_name) { @@ -109,12 +109,23 @@ impl SolcParser { /// Implements a DFS over a compiler output node and its children. /// If a natspec is found it is added to `natspecs` - fn parse(&self, natspecs: &mut Vec, contract: &str, node: &Node) { + fn parse(&self, natspecs: &mut Vec, contract: &str, node: &Node, root: bool) { + // If we're at the root contract definition node, try parsing contract-level natspec + if root { + if let Some((docs, line)) = self.get_node_docs(&node.other) { + natspecs.push(NatSpec { contract: contract.into(), function: None, docs, line }) + } + } for n in node.nodes.iter() { if let Some((function, docs, line)) = self.get_fn_data(n) { - natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + natspecs.push(NatSpec { + contract: contract.into(), + function: Some(function), + line, + docs, + }) } - self.parse(natspecs, contract, n); + self.parse(natspecs, contract, n, false); } } @@ -126,10 +137,10 @@ impl SolcParser { /// /// Return None otherwise. fn get_fn_data(&self, node: &Node) -> Option<(String, String, String)> { - if let NodeType::FunctionDefinition = node.node_type { + if node.node_type == NodeType::FunctionDefinition { let fn_data = &node.other; let fn_name: String = self.get_fn_name(fn_data)?; - let (fn_docs, docs_src_line): (String, String) = self.get_fn_docs(fn_data)?; + let (fn_docs, docs_src_line) = self.get_node_docs(fn_data)?; return Some((fn_name, fn_docs, docs_src_line)) } @@ -149,8 +160,8 @@ impl SolcParser { /// textual natspec representation, the second item is the natspec src line, in the form /// "raw:col:length". /// - `None` in case the function has not natspec comments. - fn get_fn_docs(&self, fn_data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = fn_data.get("documentation")? { + fn get_node_docs(&self, data: &BTreeMap) -> Option<(String, String)> { + if let Value::Object(fn_docs) = data.get("documentation")? { if let Value::String(comment) = fn_docs.get("text")? { if comment.contains(INLINE_CONFIG_PREFIX) { let mut src_line = fn_docs @@ -189,32 +200,55 @@ impl SolangParser { } let Ok((pt, comments)) = solang_parser::parse(src, 0) else { return }; + + // Collects natspects from the given range. + let mut handle_docs = |contract: &str, func: Option<&str>, start, end| { + let docs = solang_parser::doccomment::parse_doccomments(&comments, start, end); + natspecs.extend( + docs.into_iter() + .flat_map(|doc| doc.into_comments()) + .filter(|doc| doc.value.contains(INLINE_CONFIG_PREFIX)) + .map(|doc| NatSpec { + // not possible to obtain correct value due to solang-parser bug + // https://github.com/hyperledger/solang/issues/1658 + line: "0:0:0".to_string(), + contract: contract.to_string(), + function: func.map(|f| f.to_string()), + docs: doc.value, + }), + ); + }; + + let mut prev_item_end = 0; for item in &pt.0 { - let pt::SourceUnitPart::ContractDefinition(c) = item else { continue }; - let Some(id) = c.name.as_ref() else { continue }; + let pt::SourceUnitPart::ContractDefinition(c) = item else { + prev_item_end = item.loc().end(); + continue + }; + let Some(id) = c.name.as_ref() else { + prev_item_end = item.loc().end(); + continue + }; if id.name != contract_name { + prev_item_end = item.loc().end(); continue }; + + // Handle doc comments in between the previous contract and the current one. + handle_docs(contract_id, None, prev_item_end, item.loc().start()); + let mut prev_end = c.loc.start(); for part in &c.parts { let pt::ContractPart::FunctionDefinition(f) = part else { continue }; let start = f.loc.start(); - // Parse doc comments in between the previous function and the current one. - let docs = solang_parser::doccomment::parse_doccomments(&comments, prev_end, start); - let docs = docs - .into_iter() - .flat_map(|doc| doc.into_comments()) - .filter(|doc| doc.value.contains(INLINE_CONFIG_PREFIX)); - for doc in docs { - natspecs.push(NatSpec { - contract: contract_id.to_string(), - function: f.name.as_ref().map(|id| id.to_string()).unwrap_or_default(), - line: "0:0:0".to_string(), - docs: doc.value, - }); + // Handle doc comments in between the previous function and the current one. + if let Some(name) = &f.name { + handle_docs(contract_id, Some(name.name.as_str()), prev_end, start); } prev_end = f.loc.end(); } + + prev_item_end = item.loc().end(); } } } @@ -253,28 +287,28 @@ function f2() {} /** forge-config: default.fuzz.runs = 800 */ function f3() {} // f1 NatSpec { contract: id(), - function: "f1".to_string(), + function: Some("f1".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 600\nforge-config: default.fuzz.runs = 601".to_string(), }, // f2 NatSpec { contract: id(), - function: "f2".to_string(), + function: Some("f2".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 700".to_string(), }, // f3 NatSpec { contract: id(), - function: "f3".to_string(), + function: Some("f3".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 800".to_string(), }, // f4 NatSpec { contract: id(), - function: "f4".to_string(), + function: Some("f4".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), }, @@ -310,7 +344,7 @@ contract FuzzInlineConf is DSTest { [ NatSpec { contract: id(), - function: "testInlineConfFuzz".to_string(), + function: Some("testInlineConfFuzz".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), }, @@ -366,7 +400,7 @@ contract FuzzInlineConf is DSTest { let mut fn_data: BTreeMap = BTreeMap::new(); let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_node_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "".to_string()); } @@ -376,7 +410,7 @@ contract FuzzInlineConf is DSTest { let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_node_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "73:21:12".to_string()); } @@ -395,7 +429,7 @@ contract FuzzInlineConf is DSTest { NatSpec { contract: "dir/TestContract.t.sol:FuzzContract".to_string(), - function: "test_myFunction".to_string(), + function: Some("test_myFunction".to_string()), line: "10:12:111".to_string(), docs: conf.to_string(), } @@ -428,7 +462,7 @@ contract FuzzInlineConf2 is DSTest { natspecs, [NatSpec { contract: id(), - function: "testInlineConfFuzz1".to_string(), + function: Some("testInlineConfFuzz1".to_string()), line: default_line(), docs: "forge-config: default.fuzz.runs = 1".to_string(), },] @@ -441,11 +475,50 @@ contract FuzzInlineConf2 is DSTest { natspecs, [NatSpec { contract: id(), - function: "testInlineConfFuzz2".to_string(), + function: Some("testInlineConfFuzz2".to_string()), line: default_line(), // should not get config from previous contract docs: "forge-config: default.fuzz.runs = 2".to_string(), },] ); } + + #[test] + fn parse_contract_level_config() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +/// forge-config: default.fuzz.runs = 1 +contract FuzzInlineConf is DSTest { + /// forge-config: default.fuzz.runs = 3 + function testInlineConfFuzz1() {} + + function testInlineConfFuzz2() {} +}"#; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [ + NatSpec { + contract: id(), + function: None, + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1".to_string(), + }, + NatSpec { + contract: id(), + function: Some("testInlineConfFuzz1".to_string()), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 3".to_string(), + } + ] + ); + } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index a8570c4f9..c81827c61 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -40,7 +40,7 @@ pub struct InvariantConfig { impl Default for InvariantConfig { fn default() -> Self { - InvariantConfig { + Self { runs: 256, depth: 500, fail_on_revert: false, @@ -58,7 +58,7 @@ impl Default for InvariantConfig { impl InvariantConfig { /// Creates invariant configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. pub fn new(cache_dir: PathBuf) -> Self { - InvariantConfig { + Self { runs: 256, depth: 500, fail_on_revert: false, diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 271d53681..43b81b068 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -20,7 +20,7 @@ use foundry_compilers::{ artifacts::{ output_selection::{ContractOutputSelection, OutputSelection}, remappings::{RelativeRemapping, Remapping}, - serde_helpers, BytecodeHash, DebuggingSettings, EvmVersion, Libraries, + serde_helpers, BytecodeHash, DebuggingSettings, EofVersion, EvmVersion, Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, Settings, SettingsMetadata, Severity, }, @@ -32,14 +32,15 @@ use foundry_compilers::{ Compiler, }, error::SolcError, + solc::{CliSettings, SolcSettings}, zksolc::ZkSolcSettings, - ConfigurableArtifacts, Project, ProjectPathsConfig, + ConfigurableArtifacts, Project, ProjectPathsConfig, VyperLanguage, }; use inflector::Inflector; use regex::Regex; use revm_primitives::{FixedBytes, SpecId}; use semver::Version; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use std::{ borrow::Cow, collections::HashMap, @@ -113,6 +114,8 @@ use vyper::VyperConfig; mod zksync; pub use zksync::*; +mod bind_json; +use bind_json::BindJsonConfig; /// Foundry configuration /// @@ -256,6 +259,15 @@ pub struct Config { /// Only run tests in source files that do not match the specified glob pattern. #[serde(rename = "no_match_path", with = "from_opt_glob")] pub path_pattern_inverse: Option, + /// Only show coverage for files that do not match the specified regex pattern. + #[serde(rename = "no_match_coverage")] + pub coverage_pattern_inverse: Option, + /// Path where last test run failures are recorded. + pub test_failures_file: PathBuf, + /// Max concurrent threads to use. + pub threads: Option, + /// Whether to show test execution progress. + pub show_progress: bool, /// Configuration for fuzz testing pub fuzz: FuzzConfig, /// Configuration for invariant testing @@ -298,7 +310,7 @@ pub struct Config { pub block_difficulty: u64, /// Before merge the `block.max_hash`, after merge it is `block.prevrandao`. pub block_prevrandao: B256, - /// the `block.gaslimit` value during EVM execution + /// The `block.gaslimit` value during EVM execution. pub block_gas_limit: Option, /// The memory limit per EVM execution in bytes. /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. @@ -384,6 +396,8 @@ pub struct Config { pub fmt: FormatterConfig, /// Configuration for `forge doc` pub doc: DocConfig, + /// Configuration for `forge bind-json` + pub bind_json: BindJsonConfig, /// Configures the permissions of cheat codes that touch the file system. /// /// This includes what operations can be executed (read, write) @@ -425,6 +439,22 @@ pub struct Config { #[serde(default, skip_serializing)] pub root: RootPath, + /// Whether failed assertions should revert. + /// + /// Note that this only applies to native (cheatcode) assertions, invoked on Vm contract. + pub assertions_revert: bool, + + /// Whether `failed()` should be invoked to check if the test have failed. + pub legacy_assertions: bool, + + /// Optional additional CLI arguments to pass to `solc` binary. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extra_args: Vec, + + /// Optional EOF version. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub eof_version: Option, + /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information #[serde(rename = "__warnings", default, skip_serializing)] pub warnings: Vec, @@ -474,6 +504,7 @@ impl Config { "labels", "dependencies", "vyper", + "bind_json", ]; /// File name of config toml file @@ -495,7 +526,7 @@ impl Config { /// See `Config::figment` #[track_caller] pub fn load() -> Self { - Config::from_provider(Config::figment()) + Self::from_provider(Self::figment()) } /// Returns the current `Config` with the given `providers` preset @@ -503,7 +534,7 @@ impl Config { /// See `Config::to_figment` #[track_caller] pub fn load_with_providers(providers: FigmentProviders) -> Self { - Config::default().to_figment(providers).extract().unwrap() + Self::default().to_figment(providers).extract().unwrap() } /// Returns the current `Config` @@ -511,7 +542,7 @@ impl Config { /// See `Config::figment_with_root` #[track_caller] pub fn load_with_root(root: impl Into) -> Self { - Config::from_provider(Config::figment_with_root(root)) + Self::from_provider(Self::figment_with_root(root)) } /// Extract a `Config` from `provider`, panicking if extraction fails. @@ -565,22 +596,21 @@ impl Config { /// This will merge various providers, such as env,toml,remappings into the figment. pub fn to_figment(self, providers: FigmentProviders) -> Figment { let mut c = self; - let profile = Config::selected_profile(); + let profile = Self::selected_profile(); let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.root.0)); // merge global foundry.toml file - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - figment = Config::merge_toml_provider( + if let Some(global_toml) = Self::foundry_dir_toml().filter(|p| p.exists()) { + figment = Self::merge_toml_provider( figment, TomlFileProvider::new(None, global_toml).cached(), profile.clone(), ); } // merge local foundry.toml file - figment = Config::merge_toml_provider( + figment = Self::merge_toml_provider( figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.root.0.join(Config::FILE_NAME)) - .cached(), + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.root.0.join(Self::FILE_NAME)).cached(), profile.clone(), ); @@ -603,7 +633,7 @@ impl Config { .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) .map(|key| { let key = key.as_str(); - if Config::STANDALONE_SECTIONS.iter().any(|section| { + if Self::STANDALONE_SECTIONS.iter().any(|section| { key.starts_with(&format!("{}_", section.to_ascii_uppercase())) }) { key.replacen('_', ".", 1).into() @@ -833,6 +863,9 @@ impl Config { pub fn cleanup(&self, project: &Project) -> Result<(), SolcError> { project.cleanup()?; + // Remove last test run failures file. + let _ = fs::remove_file(&self.test_failures_file); + // Remove fuzz and invariant cache directories. let remove_test_dir = |test_dir: &Option| { if let Some(test_dir) = test_dir { @@ -889,7 +922,7 @@ impl Config { #[inline] pub fn evm_spec_id(&self) -> SpecId { if self.prague { - return SpecId::PRAGUE + return SpecId::PRAGUE_EOF } evm_spec_id(&self.evm_version) } @@ -957,6 +990,10 @@ impl Config { /// Returns configured [Vyper] compiler. pub fn vyper_compiler(&self) -> Result, SolcError> { + // Only instantiate Vyper if there are any Vyper files in the project. + if self.project_paths::().input_files_iter().next().is_none() { + return Ok(None) + } let vyper = if let Some(path) = &self.vyper.path { Some(Vyper::new(path)?) } else { @@ -1012,7 +1049,7 @@ impl Config { /// let rpc_jwt = config.get_rpc_jwt_secret().unwrap().unwrap(); /// # } /// ``` - pub fn get_rpc_jwt_secret(&self) -> Result>, UnresolvedEnvVarError> { + pub fn get_rpc_jwt_secret(&self) -> Result>, UnresolvedEnvVarError> { Ok(self.eth_rpc_jwt.as_ref().map(|jwt| Cow::Borrowed(jwt.as_str()))) } @@ -1031,7 +1068,7 @@ impl Config { /// let rpc_url = config.get_rpc_url().unwrap().unwrap(); /// # } /// ``` - pub fn get_rpc_url(&self) -> Option, UnresolvedEnvVarError>> { + pub fn get_rpc_url(&self) -> Option, UnresolvedEnvVarError>> { let maybe_alias = self.eth_rpc_url.as_ref().or(self.etherscan_api_key.as_ref())?; if let Some(alias) = self.get_rpc_url_with_alias(maybe_alias) { Some(alias) @@ -1058,7 +1095,7 @@ impl Config { pub fn get_rpc_url_with_alias( &self, maybe_alias: &str, - ) -> Option, UnresolvedEnvVarError>> { + ) -> Option, UnresolvedEnvVarError>> { let mut endpoints = self.rpc_endpoints.clone().resolved(); Some(endpoints.remove(maybe_alias)?.map(Cow::Owned)) } @@ -1077,7 +1114,7 @@ impl Config { pub fn get_rpc_url_or<'a>( &'a self, fallback: impl Into>, - ) -> Result, UnresolvedEnvVarError> { + ) -> Result, UnresolvedEnvVarError> { if let Some(url) = self.get_rpc_url() { url } else { @@ -1096,7 +1133,7 @@ impl Config { /// let rpc_url = config.get_rpc_url_or_localhost_http().unwrap(); /// # } /// ``` - pub fn get_rpc_url_or_localhost_http(&self) -> Result, UnresolvedEnvVarError> { + pub fn get_rpc_url_or_localhost_http(&self) -> Result, UnresolvedEnvVarError> { self.get_rpc_url_or("http://localhost:8545") } @@ -1255,7 +1292,7 @@ impl Config { /// - all libraries /// - the optimizer (including details, if configured) /// - evm version - pub fn solc_settings(&self) -> Result { + pub fn solc_settings(&self) -> Result { // By default if no targets are specifically selected the model checker uses all targets. // This might be too much here, so only enable assertion checks. // If users wish to enable all options they need to do so explicitly. @@ -1288,6 +1325,7 @@ impl Config { remappings: Vec::new(), // Set with `with_extra_output` below. output_selection: Default::default(), + eof_version: self.eof_version, } .with_extra_output(self.configured_artifacts_handler().output_selection()); @@ -1296,20 +1334,10 @@ impl Config { settings = settings.with_ast(); } - Ok(settings) - } - - /// Returns the configured `zksolc` `Settings` that includes: - /// - all libraries - /// - the optimizer (including details, if configured) - /// - evm version - pub fn zksync_zksolc_settings(&self) -> Result { - let libraries = match self.parsed_libraries() { - Ok(libs) => self.project_paths::().apply_lib_remappings(libs), - Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {}", e))), - }; + let cli_settings = + CliSettings { extra_args: self.extra_args.clone(), ..Default::default() }; - Ok(self.zksync.settings(libraries, self.evm_version, self.via_ir)) + Ok(SolcSettings { settings, cli_settings }) } /// Returns the configured [VyperSettings] that includes: @@ -1326,9 +1354,23 @@ impl Config { "evm.bytecode".to_string(), "evm.deployedBytecode".to_string(), ]), + search_paths: None, }) } + /// Returns the configured `zksolc` `Settings` that includes: + /// - all libraries + /// - the optimizer (including details, if configured) + /// - evm version + pub fn zksync_zksolc_settings(&self) -> Result { + let libraries = match self.parsed_libraries() { + Ok(libs) => self.project_paths::().apply_lib_remappings(libs), + Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {e}"))), + }; + + Ok(self.zksync.settings(libraries, self.evm_version, self.via_ir)) + } + /// Returns the default figment /// /// The default figment reads from the following sources, in ascending @@ -1350,7 +1392,7 @@ impl Config { /// let my_config = Config::figment().extract::(); /// ``` pub fn figment() -> Figment { - Config::default().into() + Self::default().into() } /// Returns the default figment enhanced with additional context extracted from the provided @@ -1381,7 +1423,7 @@ impl Config { let root = root.into(); let paths = ProjectPathsConfig::builder().build_with_root::<()>(&root); let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); - Config { + Self { root: paths.root.into(), src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), @@ -1392,27 +1434,27 @@ impl Config { .map(|r| RelativeRemapping::new(r, &root)) .collect(), fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), - ..Config::default() + ..Self::default() } } /// Returns the default config but with hardhat paths pub fn hardhat() -> Self { - Config { + Self { src: "contracts".into(), out: "artifacts".into(), libs: vec!["node_modules".into()], - ..Config::default() + ..Self::default() } } /// Returns the default config that uses dapptools style paths pub fn dapptools() -> Self { - Config { + Self { chain: Some(Chain::from_id(99)), block_timestamp: 0, block_number: 0, - ..Config::default() + ..Self::default() } } @@ -1440,7 +1482,7 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true`. pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> where - F: FnOnce(&Config, &mut toml_edit::DocumentMut) -> bool, + F: FnOnce(&Self, &mut toml_edit::DocumentMut) -> bool, { let config = Self::load_with_root(root).sanitized(); config.update(|doc| f(&config, doc)) @@ -1485,7 +1527,7 @@ impl Config { }) .collect(); let libs = toml_edit::value(libs); - doc[Config::PROFILE_SECTION][profile]["libs"] = libs; + doc[Self::PROFILE_SECTION][profile]["libs"] = libs; true }) } @@ -1507,7 +1549,7 @@ impl Config { // Config map always gets serialized as a table let value_table = value.as_table_mut().unwrap(); // remove standalone sections from inner table - let standalone_sections = Config::STANDALONE_SECTIONS + let standalone_sections = Self::STANDALONE_SECTIONS .iter() .filter_map(|section| { let section = section.to_string(); @@ -1516,7 +1558,7 @@ impl Config { .collect::>(); // wrap inner table in [profile.] let mut wrapping_table = [( - Config::PROFILE_SECTION.into(), + Self::PROFILE_SECTION.into(), toml::Value::Table([(self.profile.to_string(), value)].into_iter().collect()), )] .into_iter() @@ -1531,7 +1573,7 @@ impl Config { /// Returns the path to the `foundry.toml` of this `Config`. pub fn get_config_path(&self) -> PathBuf { - self.root.0.join(Config::FILE_NAME) + self.root.0.join(Self::FILE_NAME) } /// Sets the non-inlinable libraries inside a `foundry.toml` file but only if it exists the @@ -1546,26 +1588,26 @@ impl Config { let libraries: toml_edit::Value = self.libraries.iter().map(toml_edit::Value::from).collect(); let libraries = toml_edit::value(libraries); - doc[Config::PROFILE_SECTION][profile]["libraries"] = libraries; + doc[Self::PROFILE_SECTION][profile]["libraries"] = libraries; true }) } - /// Returns the selected profile + /// Returns the selected profile. /// /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE`. pub fn selected_profile() -> Profile { - Profile::from_env_or("FOUNDRY_PROFILE", Config::DEFAULT_PROFILE) + Profile::from_env_or("FOUNDRY_PROFILE", Self::DEFAULT_PROFILE) } /// Returns the path to foundry's global TOML file: `~/.foundry/foundry.toml`. pub fn foundry_dir_toml() -> Option { - Self::foundry_dir().map(|p| p.join(Config::FILE_NAME)) + Self::foundry_dir().map(|p| p.join(Self::FILE_NAME)) } /// Returns the path to foundry's config dir: `~/.foundry/`. pub fn foundry_dir() -> Option { - dirs_next::home_dir().map(|p| p.join(Config::FOUNDRY_DIR_NAME)) + dirs_next::home_dir().map(|p| p.join(Self::FOUNDRY_DIR_NAME)) } /// Returns the path to foundry's cache dir: `~/.foundry/cache`. @@ -1647,13 +1689,13 @@ impl Config { cwd = cwd.parent()?; } } - find(Env::var_or("FOUNDRY_CONFIG", Config::FILE_NAME).as_ref()) + find(Env::var_or("FOUNDRY_CONFIG", Self::FILE_NAME).as_ref()) .or_else(|| Self::foundry_dir_toml().filter(|p| p.exists())) } /// Clears the foundry cache. pub fn clean_foundry_cache() -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_cache_dir() { + if let Some(cache_dir) = Self::foundry_cache_dir() { let path = cache_dir.as_path(); let _ = fs::remove_dir_all(path); } else { @@ -1665,7 +1707,7 @@ impl Config { /// Clears the foundry cache for `chain`. pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { + if let Some(cache_dir) = Self::foundry_chain_cache_dir(chain) { let path = cache_dir.as_path(); let _ = fs::remove_dir_all(path); } else { @@ -1677,7 +1719,7 @@ impl Config { /// Clears the foundry cache for `chain` and `block`. pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_block_cache_dir(chain, block) { + if let Some(cache_dir) = Self::foundry_block_cache_dir(chain, block) { let path = cache_dir.as_path(); let _ = fs::remove_dir_all(path); } else { @@ -1689,7 +1731,7 @@ impl Config { /// Clears the foundry etherscan cache. pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_etherscan_cache_dir() { + if let Some(cache_dir) = Self::foundry_etherscan_cache_dir() { let path = cache_dir.as_path(); let _ = fs::remove_dir_all(path); } else { @@ -1701,7 +1743,7 @@ impl Config { /// Clears the foundry etherscan cache for `chain`. pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { - if let Some(cache_dir) = Config::foundry_etherscan_chain_cache_dir(chain) { + if let Some(cache_dir) = Self::foundry_etherscan_chain_cache_dir(chain) { let path = cache_dir.as_path(); let _ = fs::remove_dir_all(path); } else { @@ -1713,7 +1755,7 @@ impl Config { /// List the data in the foundry cache. pub fn list_foundry_cache() -> eyre::Result { - if let Some(cache_dir) = Config::foundry_rpc_cache_dir() { + if let Some(cache_dir) = Self::foundry_rpc_cache_dir() { let mut cache = Cache { chains: vec![] }; if !cache_dir.exists() { return Ok(cache) @@ -1736,7 +1778,7 @@ impl Config { /// List the cached data for `chain`. pub fn list_foundry_chain_cache(chain: Chain) -> eyre::Result { - let block_explorer_data_size = match Config::foundry_etherscan_chain_cache_dir(chain) { + let block_explorer_data_size = match Self::foundry_etherscan_chain_cache_dir(chain) { Some(cache_dir) => Self::get_cached_block_explorer_data(&cache_dir)?, None => { warn!("failed to access foundry_etherscan_chain_cache_dir"); @@ -1744,7 +1786,7 @@ impl Config { } }; - if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { + if let Some(cache_dir) = Self::foundry_chain_cache_dir(chain) { let blocks = Self::get_cached_blocks(&cache_dir)?; Ok(ChainCache { name: chain.to_string(), @@ -1813,8 +1855,8 @@ impl Config { }; // use [profile.] as [] - let mut profiles = vec![Config::DEFAULT_PROFILE]; - if profile != Config::DEFAULT_PROFILE { + let mut profiles = vec![Self::DEFAULT_PROFILE]; + if profile != Self::DEFAULT_PROFILE { profiles.push(profile.clone()); } let provider = toml_provider.strict_select(profiles); @@ -1823,11 +1865,11 @@ impl Config { let provider = BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); // merge the default profile as a base - if profile != Config::DEFAULT_PROFILE { - figment = figment.merge(provider.rename(Config::DEFAULT_PROFILE, profile.clone())); + if profile != Self::DEFAULT_PROFILE { + figment = figment.merge(provider.rename(Self::DEFAULT_PROFILE, profile.clone())); } // merge special keys into config - for standalone_key in Config::STANDALONE_SECTIONS { + for standalone_key in Self::STANDALONE_SECTIONS { if let Some((_, fallback)) = STANDALONE_FALLBACK_SECTIONS.iter().find(|(key, _)| standalone_key == key) { @@ -1871,7 +1913,7 @@ impl Config { } impl From for Figment { - fn from(c: Config) -> Figment { + fn from(c: Config) -> Self { c.to_figment(FigmentProviders::All) } } @@ -1934,7 +1976,7 @@ impl From for regex::Regex { impl From for RegexWrapper { fn from(re: Regex) -> Self { - RegexWrapper { inner: re } + Self { inner: re } } } @@ -2001,7 +2043,7 @@ impl Default for RootPath { impl> From

for RootPath { fn from(p: P) -> Self { - RootPath(p.into()) + Self(p.into()) } } @@ -2061,7 +2103,10 @@ impl Default for Config { profile: Self::DEFAULT_PROFILE, fs_permissions: FsPermissions::new([PathPermission::read("out")]), prague: false, + #[cfg(not(feature = "isolate-by-default"))] isolate: false, + #[cfg(feature = "isolate-by-default")] + isolate: true, root: Default::default(), src: "src".into(), test: "test".into(), @@ -2095,18 +2140,22 @@ impl Default for Config { contract_pattern_inverse: None, path_pattern: None, path_pattern_inverse: None, + coverage_pattern_inverse: None, + test_failures_file: "cache/test-failures".into(), + threads: None, + show_progress: false, fuzz: FuzzConfig::new("cache/fuzz".into()), invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, ffi: false, prompt_timeout: 120, - sender: Config::DEFAULT_SENDER, - tx_origin: Config::DEFAULT_SENDER, - initial_balance: U256::from(0xffffffffffffffffffffffffu128), + sender: Self::DEFAULT_SENDER, + tx_origin: Self::DEFAULT_SENDER, + initial_balance: U256::from((1u128 << 96) - 1), block_number: 1, fork_block_number: None, chain: None, - gas_limit: i64::MAX.into(), + gas_limit: (1u64 << 30).into(), // ~1B code_size_limit: None, gas_price: None, block_base_fee_per_gas: 0, @@ -2148,12 +2197,17 @@ impl Default for Config { build_info_path: None, fmt: Default::default(), doc: Default::default(), + bind_json: Default::default(), labels: Default::default(), unchecked_cheatcode_artifacts: false, - create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT, + create2_library_salt: Self::DEFAULT_CREATE2_LIBRARY_SALT, skip: vec![], dependencies: Default::default(), + assertions_revert: true, + legacy_assertions: false, warnings: vec![], + extra_args: vec![], + eof_version: None, _non_exhaustive: (), zksync: Default::default(), } @@ -2164,29 +2218,14 @@ impl Default for Config { /// /// Due to this limitation this type will be serialized/deserialized as String if it's larger than /// `i64` -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct GasLimit(pub u64); +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] +pub struct GasLimit(#[serde(deserialize_with = "crate::deserialize_u64_or_max")] pub u64); impl From for GasLimit { fn from(gas: u64) -> Self { Self(gas) } } -impl From for GasLimit { - fn from(gas: i64) -> Self { - Self(gas as u64) - } -} -impl From for GasLimit { - fn from(gas: i32) -> Self { - Self(gas as u64) - } -} -impl From for GasLimit { - fn from(gas: u32) -> Self { - Self(gas as u64) - } -} impl From for u64 { fn from(gas: GasLimit) -> Self { @@ -2199,7 +2238,9 @@ impl Serialize for GasLimit { where S: Serializer, { - if self.0 > i64::MAX as u64 { + if self.0 == u64::MAX { + serializer.serialize_str("max") + } else if self.0 > i64::MAX as u64 { serializer.serialize_str(&self.0.to_string()) } else { serializer.serialize_u64(self.0) @@ -2207,32 +2248,6 @@ impl Serialize for GasLimit { } } -impl<'de> Deserialize<'de> for GasLimit { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de::Error; - - #[derive(Deserialize)] - #[serde(untagged)] - enum Gas { - Number(u64), - Text(String), - } - - let gas = match Gas::deserialize(deserializer)? { - Gas::Number(num) => GasLimit(num), - Gas::Text(s) => match s.as_str() { - "max" | "MAX" | "Max" | "u64::MAX" | "u64::Max" => GasLimit(u64::MAX), - s => GasLimit(s.parse().map_err(D::Error::custom)?), - }, - }; - - Ok(gas) - } -} - /// Variants for selecting the [`Solc`] instance #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] @@ -2251,8 +2266,8 @@ impl SolcReq { /// will try to get the version from the binary. fn try_version(&self) -> Result { match self { - SolcReq::Version(version) => Ok(version.clone()), - SolcReq::Local(path) => Solc::new(path).map(|solc| solc.version), + Self::Version(version) => Ok(version.clone()), + Self::Local(path) => Solc::new(path).map(|solc| solc.version), } } } @@ -2261,9 +2276,9 @@ impl> From for SolcReq { fn from(s: T) -> Self { let s = s.as_ref(); if let Ok(v) = Version::from_str(s) { - SolcReq::Version(v) + Self::Version(v) } else { - SolcReq::Local(s.into()) + Self::Local(s.into()) } } } @@ -2529,7 +2544,7 @@ impl Provider for DappEnvCompatProvider { } } -/// Renames a profile from `from` to `to +/// Renames a profile from `from` to `to`. /// /// For example given: /// diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index d8fdbf438..1f9f5c88e 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -128,7 +128,7 @@ pub struct FallbackProfileProvider

{ impl

FallbackProfileProvider

{ /// Creates a new fallback profile provider. pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { - FallbackProfileProvider { provider, profile: profile.into(), fallback: fallback.into() } + Self { provider, profile: profile.into(), fallback: fallback.into() } } } diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 41b3fb80d..171967934 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -151,7 +151,7 @@ impl<'a> RemappingsProvider<'a> { let mut all_remappings = Remappings::new_with_remappings(user_remappings); // scan all library dirs and autodetect remappings - // todo: if a lib specifies contexts for remappings manually, we need to figure out how to + // TODO: if a lib specifies contexts for remappings manually, we need to figure out how to // resolve that if self.auto_detect_remappings { let mut lib_remappings = BTreeMap::new(); @@ -164,10 +164,8 @@ impl<'a> RemappingsProvider<'a> { .lib_paths .iter() .map(|lib| self.root.join(lib)) - .inspect(|lib| { - trace!("find all remappings in lib path: {:?}", lib); - }) - .flat_map(Remapping::find_many) + .inspect(|lib| trace!(?lib, "find all remappings")) + .flat_map(|lib| Remapping::find_many(&lib)) { // this is an additional safety check for weird auto-detected remappings if ["lib/", "src/", "contracts/"].contains(&r.name.as_str()) { diff --git a/crates/config/src/resolve.rs b/crates/config/src/resolve.rs index 981ddc886..746280f3d 100644 --- a/crates/config/src/resolve.rs +++ b/crates/config/src/resolve.rs @@ -21,7 +21,7 @@ pub struct UnresolvedEnvVarError { impl UnresolvedEnvVarError { /// Tries to resolve a value - pub fn try_resolve(&self) -> Result { + pub fn try_resolve(&self) -> Result { interpolate(&self.unresolved) } } diff --git a/crates/config/src/soldeer.rs b/crates/config/src/soldeer.rs index 511559bb9..3bb8e9a3b 100644 --- a/crates/config/src/soldeer.rs +++ b/crates/config/src/soldeer.rs @@ -4,22 +4,38 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -/// Soldeer dependencies config structure +/// Soldeer dependencies config structure when it's defined as a map #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct SoldeerDependency { +pub struct MapDependency { /// The version of the dependency pub version: String, /// The url from where the dependency was retrieved #[serde(default, skip_serializing_if = "Option::is_none")] pub url: Option, + + /// The commit in case git is used as dependency retrieval + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rev: Option, } /// Type for Soldeer configs, under dependencies tag in the foundry.toml -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct SoldeerConfig(BTreeMap); -impl AsRef for SoldeerConfig { - fn as_ref(&self) -> &SoldeerConfig { +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct SoldeerConfig(BTreeMap); + +impl AsRef for SoldeerConfig { + fn as_ref(&self) -> &Self { self } } + +/// Enum to cover both available formats for defining a dependency +/// `dep = { version = "1.1", url = "https://my-dependency" }` +/// or +/// `dep = "1.1"` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SoldeerDependencyValue { + Map(MapDependency), + Str(String), +} diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 17af4789d..b58565c4c 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -236,31 +236,31 @@ where } } -/// Deserialize an usize or -pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result +/// Deserialize a `u64` or "max" for `u64::MAX`. +pub(crate) fn deserialize_u64_or_max<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] enum Val { - Number(usize), - Text(String), + Number(u64), + String(String), } - let num = match Val::deserialize(deserializer)? { - Val::Number(num) => num, - Val::Text(s) => { - match s.as_str() { - "max" | "MAX" | "Max" => { - // toml limitation - i64::MAX as usize - } - s => s.parse::().map_err(D::Error::custom).unwrap(), - } - } - }; - Ok(num) + match Val::deserialize(deserializer)? { + Val::Number(num) => Ok(num), + Val::String(s) if s.eq_ignore_ascii_case("max") => Ok(u64::MAX), + Val::String(s) => s.parse::().map_err(D::Error::custom), + } +} + +/// Deserialize a `usize` or "max" for `usize::MAX`. +pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom) } /// Helper type to parse both `u64` and `U256` @@ -274,10 +274,10 @@ pub enum Numeric { } impl From for U256 { - fn from(n: Numeric) -> U256 { + fn from(n: Numeric) -> Self { match n { Numeric::U256(n) => n, - Numeric::Num(n) => U256::from(n), + Numeric::Num(n) => Self::from(n), } } } diff --git a/crates/config/src/vyper.rs b/crates/config/src/vyper.rs index 2af46b4b6..7b2f0a54d 100644 --- a/crates/config/src/vyper.rs +++ b/crates/config/src/vyper.rs @@ -4,7 +4,7 @@ use foundry_compilers::artifacts::vyper::VyperOptimizationMode; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct VyperConfig { /// Vyper optimization mode. "gas", "none" or "codesize" #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index ac73cfd01..5223cf01f 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -3,6 +3,7 @@ use foundry_compilers::{ zksolc::output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, EvmVersion, Libraries, }, + solc::CliSettings, zksolc::settings::{ BytecodeHash, Optimizer, OptimizerDetails, SettingsMetadata, ZkSolcSettings, }, @@ -126,6 +127,7 @@ impl ZkSyncConfig { }), }, solc: self.solc_path.clone(), + cli_settings: CliSettings::default(), } } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 812c6fdad..9f96eb7f0 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] foundry-common.workspace = true foundry-compilers.workspace = true -foundry-evm-core.workspace = true foundry-evm-traces.workspace = true revm-inspectors.workspace = true @@ -26,3 +25,4 @@ eyre.workspace = true ratatui = { version = "0.26", default-features = false, features = ["crossterm"] } revm.workspace = true tracing.workspace = true +serde.workspace = true diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index ed5da9342..678ae8672 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -12,3 +12,6 @@ mod op; mod tui; pub use tui::{Debugger, DebuggerBuilder, ExitReason}; + +mod node; +pub use node::DebugNode; diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs new file mode 100644 index 000000000..83477f006 --- /dev/null +++ b/crates/debugger/src/node.rs @@ -0,0 +1,85 @@ +use alloy_primitives::{Address, Bytes}; +use foundry_evm_traces::{CallKind, CallTraceArena}; +use revm_inspectors::tracing::types::{CallTraceStep, TraceMemberOrder}; +use serde::{Deserialize, Serialize}; + +/// Represents a part of the execution frame before the next call or end of the execution. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct DebugNode { + /// Execution context. + /// + /// Note that this is the address of the *code*, not necessarily the address of the storage. + pub address: Address, + /// The kind of call this is. + pub kind: CallKind, + /// Calldata of the call. + pub calldata: Bytes, + /// The debug steps. + pub steps: Vec, +} + +impl DebugNode { + /// Creates a new debug node. + pub fn new( + address: Address, + kind: CallKind, + steps: Vec, + calldata: Bytes, + ) -> Self { + Self { address, kind, steps, calldata } + } +} + +/// Flattens given [CallTraceArena] into a list of [DebugNode]s. +/// +/// This is done by recursively traversing the call tree and collecting the steps in-between the +/// calls. +pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec) { + #[derive(Debug, Clone, Copy)] + struct PendingNode { + node_idx: usize, + steps_count: usize, + } + + fn inner(arena: &CallTraceArena, node_idx: usize, out: &mut Vec) { + let mut pending = PendingNode { node_idx, steps_count: 0 }; + let node = &arena.nodes()[node_idx]; + for order in node.ordering.iter() { + match order { + TraceMemberOrder::Call(idx) => { + out.push(pending); + pending.steps_count = 0; + inner(arena, node.children[*idx], out); + } + TraceMemberOrder::Step(_) => { + pending.steps_count += 1; + } + _ => {} + } + } + out.push(pending); + } + let mut nodes = Vec::new(); + inner(&arena, 0, &mut nodes); + + let mut arena_nodes = arena.into_nodes(); + + for pending in nodes { + let steps = { + let other_steps = + arena_nodes[pending.node_idx].trace.steps.split_off(pending.steps_count); + std::mem::replace(&mut arena_nodes[pending.node_idx].trace.steps, other_steps) + }; + + // Skip nodes with empty steps as there's nothing to display for them. + if steps.is_empty() { + continue + } + + let call = &arena_nodes[pending.node_idx].trace; + let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() }; + let node = DebugNode::new(call.address, call.kind, steps, calldata); + + out.push(node); + } +} diff --git a/crates/debugger/src/op.rs b/crates/debugger/src/op.rs index 486fbe09f..bc8e96ccb 100644 --- a/crates/debugger/src/op.rs +++ b/crates/debugger/src/op.rs @@ -1,3 +1,6 @@ +use alloy_primitives::Bytes; +use revm::interpreter::opcode; + /// Named parameter of an EVM opcode. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct OpcodeParam { @@ -8,10 +11,35 @@ pub(crate) struct OpcodeParam { } impl OpcodeParam { - /// Returns the list of named parameters for the given opcode. + /// Returns the list of named parameters for the given opcode, accounts for special opcodes + /// requiring immediate bytes to determine stack items. #[inline] - pub(crate) fn of(op: u8) -> &'static [Self] { - MAP[op as usize] + pub(crate) fn of(op: u8, immediate: Option<&Bytes>) -> Option> { + match op { + // Handle special cases requiring immediate bytes + opcode::DUPN => immediate + .and_then(|i| i.first().copied()) + .map(|i| vec![Self { name: "dup_value", index: i as usize }]), + opcode::SWAPN => immediate.and_then(|i| { + i.first().map(|i| { + vec![ + Self { name: "a", index: 1 }, + Self { name: "swap_value", index: *i as usize }, + ] + }) + }), + opcode::EXCHANGE => immediate.and_then(|i| { + i.first().map(|imm| { + let n = (imm >> 4) + 1; + let m = (imm & 0xf) + 1; + vec![ + Self { name: "value1", index: n as usize }, + Self { name: "value2", index: m as usize }, + ] + }) + }), + _ => Some(MAP[op as usize].to_vec()), + } } } @@ -41,11 +69,12 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { // https://www.evm.codes // https://github.com/smlxl/evm.codes - // https://github.com/smlxl/evm.codes/blob/HEAD/opcodes.json + // https://github.com/klkvr/evm.codes + // https://github.com/klkvr/evm.codes/blob/HEAD/opcodes.json // jq -rf opcodes.jq opcodes.json /* def mkargs(input): - input | split(" | ") | to_entries | map("\(.key): \(.value)") | join(", "); + input | split(" | ") | to_entries | map("\(.key): \"\(.value)\"") | join(", "); to_entries[] | "0x\(.key)(\(mkargs(.value.input)))," */ @@ -265,10 +294,10 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xcd(), 0xce(), 0xcf(), - 0xd0(), + 0xd0(0: "offset"), 0xd1(), 0xd2(), - 0xd3(), + 0xd3(0: "memOffset", 1: "offset", 2: "size"), 0xd4(), 0xd5(), 0xd6(), @@ -282,8 +311,8 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xde(), 0xdf(), 0xe0(), - 0xe1(), - 0xe2(), + 0xe1(0: "condition"), + 0xe2(0: "case"), 0xe3(), 0xe4(), 0xe5(), @@ -293,9 +322,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xe9(), 0xea(), 0xeb(), - 0xec(), + 0xec(0: "value", 1: "salt", 2: "offset", 3: "size"), 0xed(), - 0xee(), + 0xee(0: "offset", 1: "size"), 0xef(), 0xf0(0: "value", 1: "offset", 2: "size"), 0xf1(0: "gas", 1: "address", 2: "value", 3: "argsOffset", 4: "argsSize", 5: "retOffset", 6: "retSize"), @@ -304,11 +333,11 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xf4(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), 0xf5(0: "value", 1: "offset", 2: "size", 3: "salt"), 0xf6(), - 0xf7(), - 0xf8(), - 0xf9(), + 0xf7(0: "offset"), + 0xf8(0: "address", 1: "argsOffset", 2: "argsSize", 3: "value"), + 0xf9(0: "address", 1: "argsOffset", 2: "argsSize"), 0xfa(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), - 0xfb(), + 0xfb(0: "address", 1: "argsOffset", 2: "argsSize"), 0xfc(), 0xfd(0: "offset", 1: "size"), 0xfe(), diff --git a/crates/debugger/src/tui/builder.rs b/crates/debugger/src/tui/builder.rs index 6289b0b88..81632dcca 100644 --- a/crates/debugger/src/tui/builder.rs +++ b/crates/debugger/src/tui/builder.rs @@ -1,10 +1,9 @@ //! TUI debugger builder. -use crate::Debugger; +use crate::{node::flatten_call_trace, DebugNode, Debugger}; use alloy_primitives::Address; -use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name}; -use foundry_evm_core::debug::{DebugArena, DebugNodeFlat}; -use foundry_evm_traces::CallTraceDecoder; +use foundry_common::{evm::Breakpoints, get_contract_name}; +use foundry_evm_traces::{debug::ContractSources, CallTraceArena, CallTraceDecoder, Traces}; use std::collections::HashMap; /// Debugger builder. @@ -12,7 +11,7 @@ use std::collections::HashMap; #[must_use = "builders do nothing unless you call `build` on them"] pub struct DebuggerBuilder { /// Debug traces returned from the EVM execution. - debug_arena: Vec, + debug_arena: Vec, /// Identified contracts. identified_contracts: HashMap, /// Map of source files. @@ -30,17 +29,17 @@ impl DebuggerBuilder { /// Extends the debug arena. #[inline] - pub fn debug_arenas(mut self, arena: &[DebugArena]) -> Self { - for arena in arena { - self = self.debug_arena(arena); + pub fn traces(mut self, traces: Traces) -> Self { + for (_, arena) in traces { + self = self.trace_arena(arena); } self } /// Extends the debug arena. #[inline] - pub fn debug_arena(mut self, arena: &DebugArena) -> Self { - arena.flatten_to(0, &mut self.debug_arena); + pub fn trace_arena(mut self, arena: CallTraceArena) -> Self { + flatten_call_trace(arena, &mut self.debug_arena); self } diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 22ea7d5d3..6792145fe 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -1,10 +1,10 @@ //! Debugger context and event handler implementation. -use crate::{Debugger, ExitReason}; -use alloy_primitives::Address; +use crate::{DebugNode, Debugger, ExitReason}; +use alloy_primitives::{hex, Address}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; -use foundry_evm_core::debug::{DebugNodeFlat, DebugStep}; -use revm_inspectors::tracing::types::CallKind; +use revm::interpreter::OpCode; +use revm_inspectors::tracing::types::{CallKind, CallTraceStep}; use std::ops::ControlFlow; /// This is currently used to remember last scroll position so screen doesn't wiggle as much. @@ -84,11 +84,11 @@ impl<'a> DebuggerContext<'a> { self.gen_opcode_list(); } - pub(crate) fn debug_arena(&self) -> &[DebugNodeFlat] { + pub(crate) fn debug_arena(&self) -> &[DebugNode] { &self.debugger.debug_arena } - pub(crate) fn debug_call(&self) -> &DebugNodeFlat { + pub(crate) fn debug_call(&self) -> &DebugNode { &self.debug_arena()[self.draw_memory.inner_call_index] } @@ -103,19 +103,21 @@ impl<'a> DebuggerContext<'a> { } /// Returns the current debug steps. - pub(crate) fn debug_steps(&self) -> &[DebugStep] { + pub(crate) fn debug_steps(&self) -> &[CallTraceStep] { &self.debug_call().steps } /// Returns the current debug step. - pub(crate) fn current_step(&self) -> &DebugStep { + pub(crate) fn current_step(&self) -> &CallTraceStep { &self.debug_steps()[self.current_step] } fn gen_opcode_list(&mut self) { self.opcode_list.clear(); let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps; - self.opcode_list.extend(debug_steps.iter().map(DebugStep::pretty_opcode)); + for step in debug_steps { + self.opcode_list.push(pretty_opcode(step)); + } } fn gen_opcode_list_if_necessary(&mut self) { @@ -127,8 +129,8 @@ impl<'a> DebuggerContext<'a> { fn active_buffer(&self) -> &[u8] { match self.active_buffer { - BufferKind::Memory => &self.current_step().memory, - BufferKind::Calldata => &self.current_step().calldata, + BufferKind::Memory => self.current_step().memory.as_ref().unwrap().as_bytes(), + BufferKind::Calldata => &self.debug_call().calldata, BufferKind::Returndata => &self.current_step().returndata, } } @@ -186,7 +188,8 @@ impl DebuggerContext<'_> { }), // Scroll down the stack KeyCode::Char('J') => self.repeat(|this| { - let max_stack = this.current_step().stack.len().saturating_sub(1); + let max_stack = + this.current_step().stack.as_ref().map_or(0, |s| s.len()).saturating_sub(1); if this.draw_memory.current_stack_startline < max_stack { this.draw_memory.current_stack_startline += 1; } @@ -227,20 +230,20 @@ impl DebuggerContext<'_> { // Step forward KeyCode::Char('s') => self.repeat(|this| { - let remaining_ops = &this.opcode_list[this.current_step..]; - if let Some((i, _)) = remaining_ops.iter().enumerate().skip(1).find(|&(i, op)| { - let prev = &remaining_ops[i - 1]; - let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; - let is_jumpdest = op == "JUMPDEST"; - prev_is_jump && is_jumpdest - }) { - this.current_step += i; + let remaining_steps = &this.debug_steps()[this.current_step..]; + if let Some((i, _)) = + remaining_steps.iter().enumerate().skip(1).find(|(i, step)| { + let prev = &remaining_steps[*i - 1]; + is_jump(step, prev) + }) + { + this.current_step += i } }), // Step backwards KeyCode::Char('a') => self.repeat(|this| { - let ops = &this.opcode_list[..this.current_step]; + let ops = &this.debug_steps()[..this.current_step]; this.current_step = ops .iter() .enumerate() @@ -248,9 +251,7 @@ impl DebuggerContext<'_> { .rev() .find(|&(i, op)| { let prev = &ops[i - 1]; - let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; - let is_jumpdest = op == "JUMPDEST"; - prev_is_jump && is_jumpdest + is_jump(op, prev) }) .map(|(i, _)| i) .unwrap_or_default(); @@ -345,3 +346,35 @@ fn buffer_as_number(s: &str) -> usize { const MAX: usize = 100_000; s.parse().unwrap_or(MIN).clamp(MIN, MAX) } + +fn pretty_opcode(step: &CallTraceStep) -> String { + if let Some(immediate) = step.immediate_bytes.as_ref().filter(|b| !b.is_empty()) { + format!("{}(0x{})", step.op, hex::encode(immediate)) + } else { + step.op.to_string() + } +} + +fn is_jump(step: &CallTraceStep, prev: &CallTraceStep) -> bool { + if !matches!( + prev.op, + OpCode::JUMP | + OpCode::JUMPI | + OpCode::JUMPF | + OpCode::RJUMP | + OpCode::RJUMPI | + OpCode::RJUMPV | + OpCode::CALLF | + OpCode::RETF + ) { + return false + } + + let immediate_len = prev.immediate_bytes.as_ref().map_or(0, |b| b.len()); + + if step.pc != prev.pc + 1 + immediate_len { + true + } else { + step.code_section_idx != prev.code_section_idx + } +} diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 7ac0c64e5..509b986fd 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -3,9 +3,8 @@ use super::context::{BufferKind, DebuggerContext}; use crate::op::OpcodeParam; use alloy_primitives::U256; -use foundry_compilers::{ - artifacts::sourcemap::SourceElement, compilers::multi::MultiCompilerLanguage, -}; +use foundry_compilers::artifacts::sourcemap::SourceElement; +use foundry_evm_traces::debug::SourceData; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, @@ -201,6 +200,7 @@ impl DebuggerContext<'_> { CallKind::CallCode => "Contract callcode", CallKind::DelegateCall => "Contract delegatecall", CallKind::AuthCall => "Contract authcall", + CallKind::EOFCreate => "EOF contract creation", }; let title = format!( "{} {} ", @@ -213,7 +213,7 @@ impl DebuggerContext<'_> { } fn src_text(&self, area: Rect) -> (Text<'_>, Option<&str>) { - let (source_element, source_code, source_file) = match self.src_map() { + let (source_element, source) = match self.src_map() { Ok(r) => r, Err(e) => return (Text::from(e), None), }; @@ -224,15 +224,16 @@ impl DebuggerContext<'_> { // minus `sum(push_bytes[..pc])`. let offset = source_element.offset() as usize; let len = source_element.length() as usize; - let max = source_code.len(); + let max = source.source.len(); // Split source into before, relevant, and after chunks, split by line, for formatting. let actual_start = offset.min(max); let actual_end = (offset + len).min(max); - let mut before: Vec<_> = source_code[..actual_start].split_inclusive('\n').collect(); - let actual: Vec<_> = source_code[actual_start..actual_end].split_inclusive('\n').collect(); - let mut after: VecDeque<_> = source_code[actual_end..].split_inclusive('\n').collect(); + let mut before: Vec<_> = source.source[..actual_start].split_inclusive('\n').collect(); + let actual: Vec<_> = + source.source[actual_start..actual_end].split_inclusive('\n').collect(); + let mut after: VecDeque<_> = source.source[actual_end..].split_inclusive('\n').collect(); let num_lines = before.len() + actual.len() + after.len(); let height = area.height as usize; @@ -279,7 +280,7 @@ impl DebuggerContext<'_> { // Highlighted text: cyan, bold. let h_text = Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD); - let mut lines = SourceLines::new(decimal_digits(num_lines)); + let mut lines = SourceLines::new(start_line, end_line); // We check if there is other text on the same line before the highlight starts. if let Some(last) = before.pop() { @@ -337,74 +338,24 @@ impl DebuggerContext<'_> { } } - (Text::from(lines.lines), Some(source_file)) + (Text::from(lines.lines), source.path.to_str()) } /// Returns source map, source code and source name of the current line. - fn src_map(&self) -> Result<(SourceElement, &str, &str), String> { + fn src_map(&self) -> Result<(SourceElement, &SourceData), String> { let address = self.address(); let Some(contract_name) = self.debugger.identified_contracts.get(address) else { return Err(format!("Unknown contract at address {address}")); }; - let Some(mut files_source_code) = - self.debugger.contracts_sources.get_sources(contract_name) - else { - return Err(format!("No source map index for contract {contract_name}")); - }; - - let Some((create_map, rt_map)) = self.debugger.pc_ic_maps.get(contract_name) else { - return Err(format!("No PC-IC maps for contract {contract_name}")); - }; - - let is_create = matches!(self.call_kind(), CallKind::Create | CallKind::Create2); - let pc = self.current_step().pc; - let Some((source_element, source_code, source_file)) = - files_source_code.find_map(|(artifact, source)| { - let bytecode = if is_create { - &artifact.bytecode.bytecode - } else { - artifact.bytecode.deployed_bytecode.bytecode.as_ref()? - }; - let source_map = bytecode.source_map()?.expect("failed to parse"); - - let pc_ic_map = if is_create { create_map } else { rt_map }; - let ic = pc_ic_map.get(pc)?; - - // Solc indexes source maps by instruction counter, but Vyper indexes by program - // counter. - let source_element = if matches!(source.language, MultiCompilerLanguage::Solc(_)) { - source_map.get(ic)? - } else { - source_map.get(pc)? - }; - // if the source element has an index, find the sourcemap for that index - let res = source_element - .index() - // if index matches current file_id, return current source code - .and_then(|index| { - (index == artifact.file_id) - .then(|| (source_element.clone(), source.source.as_str(), &source.name)) - }) - .or_else(|| { - // otherwise find the source code for the element's index - self.debugger - .contracts_sources - .sources_by_id - .get(&artifact.build_id)? - .get(&source_element.index()?) - .map(|source| { - (source_element.clone(), source.source.as_str(), &source.name) - }) - }); - - res - }) - else { - return Err(format!("No source map for contract {contract_name}")); - }; - - Ok((source_element, source_code, source_file)) + self.debugger + .contracts_sources + .find_source_mapping( + contract_name, + self.current_step().pc, + self.debug_call().kind.is_any_create(), + ) + .ok_or_else(|| format!("No source map for contract {contract_name}")) } fn draw_op_list(&self, f: &mut Frame<'_>, area: Rect) { @@ -426,10 +377,11 @@ impl DebuggerContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {}", + "Address: {} | PC: {} | Gas used in call: {} | Code section: {}", self.address(), self.current_step().pc, - self.current_step().total_gas_used, + self.current_step().gas_used, + self.current_step().code_section_idx, ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) @@ -443,58 +395,69 @@ impl DebuggerContext<'_> { fn draw_stack(&self, f: &mut Frame<'_>, area: Rect) { let step = self.current_step(); - let stack = &step.stack; + let stack = step.stack.as_ref(); + let stack_len = stack.map_or(0, |s| s.len()); - let min_len = decimal_digits(stack.len()).max(2); + let min_len = decimal_digits(stack_len).max(2); - let params = OpcodeParam::of(step.instruction); + let params = OpcodeParam::of(step.op.get(), step.immediate_bytes.as_ref()); let text: Vec> = stack - .iter() - .rev() - .enumerate() - .skip(self.draw_memory.current_stack_startline) - .map(|(i, stack_item)| { - let param = params.iter().find(|param| param.index == i); - - let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); - - // Stack index. - spans.push(Span::styled(format!("{i:0min_len$}| "), Style::new().fg(Color::White))); - - // Item hex bytes. - hex_bytes_spans(&stack_item.to_be_bytes::<32>(), &mut spans, |_, _| { - if param.is_some() { - Style::new().fg(Color::Cyan) - } else { - Style::new().fg(Color::White) - } - }); + .map(|stack| { + stack + .iter() + .rev() + .enumerate() + .skip(self.draw_memory.current_stack_startline) + .map(|(i, stack_item)| { + let param = params + .as_ref() + .and_then(|params| params.iter().find(|param| param.index == i)); + + let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); + + // Stack index. + spans.push(Span::styled( + format!("{i:0min_len$}| "), + Style::new().fg(Color::White), + )); + + // Item hex bytes. + hex_bytes_spans(&stack_item.to_be_bytes::<32>(), &mut spans, |_, _| { + if param.is_some() { + Style::new().fg(Color::Cyan) + } else { + Style::new().fg(Color::White) + } + }); - if self.stack_labels { - if let Some(param) = param { - spans.push(Span::raw("| ")); - spans.push(Span::raw(param.name)); - } - } + if self.stack_labels { + if let Some(param) = param { + spans.push(Span::raw("| ")); + spans.push(Span::raw(param.name)); + } + } - spans.push(Span::raw("\n")); + spans.push(Span::raw("\n")); - Line::from(spans) + Line::from(spans) + }) + .collect() }) - .collect(); + .unwrap_or_default(); - let title = format!("Stack: {}", stack.len()); + let title = format!("Stack: {stack_len}"); let block = Block::default().title(title).borders(Borders::ALL); let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); f.render_widget(paragraph, area); } fn draw_buffer(&self, f: &mut Frame<'_>, area: Rect) { + let call = self.debug_call(); let step = self.current_step(); let buf = match self.active_buffer { - BufferKind::Memory => step.memory.as_ref(), - BufferKind::Calldata => step.calldata.as_ref(), + BufferKind::Memory => step.memory.as_ref().unwrap().as_ref(), + BufferKind::Calldata => call.calldata.as_ref(), BufferKind::Returndata => step.returndata.as_ref(), }; @@ -506,18 +469,20 @@ impl DebuggerContext<'_> { let mut write_offset = None; let mut write_size = None; let mut color = None; - let stack_len = step.stack.len(); + let stack_len = step.stack.as_ref().map_or(0, |s| s.len()); if stack_len > 0 { - if let Some(accesses) = get_buffer_accesses(step.instruction, &step.stack) { - if let Some(read_access) = accesses.read { - offset = Some(read_access.1.offset); - size = Some(read_access.1.size); - color = Some(Color::Cyan); - } - if let Some(write_access) = accesses.write { - if self.active_buffer == BufferKind::Memory { - write_offset = Some(write_access.offset); - write_size = Some(write_access.size); + if let Some(stack) = step.stack.as_ref() { + if let Some(accesses) = get_buffer_accesses(step.op.get(), stack) { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + size = Some(read_access.1.size); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write { + if self.active_buffer == BufferKind::Memory { + write_offset = Some(write_access.offset); + write_size = Some(write_access.size); + } } } } @@ -530,13 +495,15 @@ impl DebuggerContext<'_> { if self.current_step > 0 { let prev_step = self.current_step - 1; let prev_step = &self.debug_steps()[prev_step]; - if let Some(write_access) = - get_buffer_accesses(prev_step.instruction, &prev_step.stack).and_then(|a| a.write) - { - if self.active_buffer == BufferKind::Memory { - offset = Some(write_access.offset); - size = Some(write_access.size); - color = Some(Color::Green); + if let Some(stack) = prev_step.stack.as_ref() { + if let Some(write_access) = + get_buffer_accesses(prev_step.op.get(), stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Green); + } } } } @@ -625,12 +592,13 @@ impl DebuggerContext<'_> { /// Wrapper around a list of [`Line`]s that prepends the line number on each new line. struct SourceLines<'a> { lines: Vec>, + start_line: usize, max_line_num: usize, } impl<'a> SourceLines<'a> { - fn new(max_line_num: usize) -> Self { - Self { lines: Vec::new(), max_line_num } + fn new(start_line: usize, end_line: usize) -> Self { + Self { lines: Vec::new(), start_line, max_line_num: decimal_digits(end_line) } } fn push(&mut self, line_number_style: Style, line: &'a str, line_style: Style) { @@ -640,8 +608,11 @@ impl<'a> SourceLines<'a> { fn push_raw(&mut self, line_number_style: Style, spans: &[Span<'a>]) { let mut line_spans = Vec::with_capacity(4); - let line_number = - format!("{number: >width$} ", number = self.lines.len() + 1, width = self.max_line_num); + let line_number = format!( + "{number: >width$} ", + number = self.start_line + self.lines.len() + 1, + width = self.max_line_num + ); line_spans.push(Span::styled(line_number, line_number_style)); // Space between line number and line text. @@ -669,13 +640,14 @@ struct BufferAccesses { /// The memory_access variable stores the index on the stack that indicates the buffer /// offset/size accessed by the given opcode: -/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) -/// >= 1: the stack index -/// 0: no memory access -/// -1: a fixed size of 32 bytes -/// -2: a fixed size of 1 byte +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) +/// \>= 1: the stack index +/// 0: no memory access +/// -1: a fixed size of 32 bytes +/// -2: a fixed size of 1 byte +/// /// The return value is a tuple about accessed buffer region by the given opcode: -/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { let buffer_access = match op { opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => { @@ -696,6 +668,13 @@ fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))), + opcode::RETURNDATALOAD => (Some((BufferKind::Returndata, 1, -1)), None), + opcode::EOFCREATE => (Some((BufferKind::Memory, 3, 4)), None), + opcode::RETURNCONTRACT => (Some((BufferKind::Memory, 1, 2)), None), + opcode::DATACOPY => (None, Some((1, 3))), + opcode::EXTCALL | opcode::EXTSTATICCALL | opcode::EXTDELEGATECALL => { + (Some((BufferKind::Memory, 2, 3)), None) + } _ => Default::default(), }; diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index c810440e5..6b522250b 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -7,14 +7,14 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use eyre::Result; -use foundry_common::{compile::ContractSources, evm::Breakpoints}; -use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap}; +use foundry_common::evm::Breakpoints; +use foundry_evm_traces::debug::ContractSources; use ratatui::{ backend::{Backend, CrosstermBackend}, Terminal, }; use std::{ - collections::{BTreeMap, HashMap}, + collections::HashMap, io, ops::ControlFlow, sync::{mpsc, Arc}, @@ -28,6 +28,8 @@ pub use builder::DebuggerBuilder; mod context; use context::DebuggerContext; +use crate::DebugNode; + mod draw; type DebuggerTerminal = Terminal>; @@ -41,12 +43,10 @@ pub enum ExitReason { /// The TUI debugger. pub struct Debugger { - debug_arena: Vec, + debug_arena: Vec, identified_contracts: HashMap, /// Source map of contract sources contracts_sources: ContractSources, - /// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code) - pc_ic_maps: BTreeMap, breakpoints: Breakpoints, } @@ -59,24 +59,12 @@ impl Debugger { /// Creates a new debugger. pub fn new( - debug_arena: Vec, + debug_arena: Vec, identified_contracts: HashMap, contracts_sources: ContractSources, breakpoints: Breakpoints, ) -> Self { - let pc_ic_maps = contracts_sources - .entries() - .filter_map(|(name, artifact, _)| { - Some(( - name.to_owned(), - ( - PcIcMap::new(artifact.bytecode.bytecode.bytes()?), - PcIcMap::new(artifact.bytecode.deployed_bytecode.bytes()?), - ), - )) - }) - .collect(); - Self { debug_arena, identified_contracts, contracts_sources, pc_ic_maps, breakpoints } + Self { debug_arena, identified_contracts, contracts_sources, breakpoints } } /// Starts the debugger TUI. Terminates the current process on failure or user exit. diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 0f8e91c79..280dcfd0d 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; /// The natspec comment tag explaining the purpose of the comment. /// See: . -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum CommentTag { /// A title that should describe the contract/interface Title, @@ -56,7 +56,7 @@ impl CommentTag { /// The natspec documentation comment. /// /// Ref: -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Comment { /// The doc comment tag. pub tag: CommentTag, diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index b93f0d199..999758ceb 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -147,7 +147,7 @@ impl ParseItem { } /// A wrapper type around pt token. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ParseSource { /// Source contract definition. Contract(Box), diff --git a/crates/evm/abi/Cargo.toml b/crates/evm/abi/Cargo.toml new file mode 100644 index 000000000..892963acd --- /dev/null +++ b/crates/evm/abi/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "foundry-evm-abi" +description = "Solidity ABI-related utilities and `sol!` definitions" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-common-fmt.workspace = true +foundry-macros.workspace = true + +alloy-primitives.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } + +derive_more.workspace = true +itertools.workspace = true +once_cell.workspace = true +rustc-hash.workspace = true + +[dev-dependencies] +foundry-test-utils.workspace = true diff --git a/crates/evm/abi/src/HardhatConsole.json b/crates/evm/abi/src/HardhatConsole.json new file mode 100644 index 000000000..54e6d46df --- /dev/null +++ b/crates/evm/abi/src/HardhatConsole.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/evm/abi/src/console.py b/crates/evm/abi/src/console.py new file mode 100755 index 000000000..e0ca8aa89 --- /dev/null +++ b/crates/evm/abi/src/console.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import json +import re +import subprocess +import sys + + +def main(): + if len(sys.argv) < 4: + print( + f"Usage: {sys.argv[0]} " + ) + sys.exit(1) + [console_file, abi_file, patches_file] = sys.argv[1:4] + + # Parse signatures from `console.sol`'s string literals + console_sol = open(console_file).read() + sig_strings = re.findall( + r'"(log.*?)"', + console_sol, + ) + raw_sigs = [s.strip().strip('"') for s in sig_strings] + sigs = [ + s.replace("string", "string memory").replace("bytes)", "bytes memory)") + for s in raw_sigs + ] + sigs = list(set(sigs)) + + # Get HardhatConsole ABI + s = "interface HardhatConsole{\n" + for sig in sigs: + s += f"function {sig} external pure;\n" + s += "\n}" + r = subprocess.run( + ["solc", "-", "--combined-json", "abi"], + input=s.encode("utf8"), + capture_output=True, + ) + combined = json.loads(r.stdout.strip()) + 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/hardhat.rs b/crates/evm/abi/src/console/hardhat.rs new file mode 100644 index 000000000..1ec082841 --- /dev/null +++ b/crates/evm/abi/src/console/hardhat.rs @@ -0,0 +1,62 @@ +use alloy_primitives::{address, Address, Selector}; +use alloy_sol_types::sol; +use foundry_common_fmt::*; +use foundry_macros::ConsoleFmt; +use once_cell::sync::Lazy; +use rustc_hash::FxHashMap; + +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: Lazy> = + Lazy::new(|| FxHashMap::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/core/src/abi/console.rs b/crates/evm/abi/src/console/mod.rs similarity index 98% rename from crates/evm/core/src/abi/console.rs rename to crates/evm/abi/src/console/mod.rs index e9757a476..3f10c769e 100644 --- a/crates/evm/core/src/abi/console.rs +++ b/crates/evm/abi/src/console/mod.rs @@ -3,6 +3,9 @@ use alloy_sol_types::sol; use derive_more::Display; use itertools::Itertools; +mod hardhat; +pub use hardhat::*; + // TODO: Use `UiFmt` sol! { diff --git a/crates/evm/abi/src/console/patches.rs b/crates/evm/abi/src/console/patches.rs new file mode 100644 index 000000000..ad63a9fe6 --- /dev/null +++ b/crates/evm/abi/src/console/patches.rs @@ -0,0 +1,674 @@ +[ + // `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 new file mode 100644 index 000000000..6a31fe550 --- /dev/null +++ b/crates/evm/abi/src/lib.rs @@ -0,0 +1,7 @@ +//! Solidity ABI-related utilities and [`sol!`](alloy_sol_types::sol) definitions. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +mod console; +pub use console::*; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 8885ec34e..edfca5892 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -17,8 +17,8 @@ workspace = true foundry-cheatcodes-spec.workspace = true foundry-common.workspace = true foundry-config.workspace = true -foundry-macros.workspace = true foundry-zksync-core.workspace = true +foundry-evm-abi.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-genesis.workspace = true @@ -34,6 +34,7 @@ alloy-rpc-types.workspace = true alloy-serde.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true +foundry-fork-db.workspace = true revm = { workspace = true, features = [ "std", @@ -48,14 +49,10 @@ revm = { workspace = true, features = [ ] } revm-inspectors.workspace = true -arrayvec.workspace = true auto_impl.workspace = true -derive_more.workspace = true eyre.workspace = true futures.workspace = true -hex.workspace = true itertools.workspace = true -once_cell.workspace = true parking_lot.workspace = true rustc-hash.workspace = true serde.workspace = true @@ -63,7 +60,6 @@ serde_json.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true -url.workspace = true [dev-dependencies] foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/abi/HardhatConsole.json b/crates/evm/core/src/abi/HardhatConsole.json deleted file mode 100644 index 4013d8753..000000000 --- a/crates/evm/core/src/abi/HardhatConsole.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] \ No newline at end of file diff --git a/crates/evm/core/src/abi/hardhat_console.rs b/crates/evm/core/src/abi/hardhat_console.rs deleted file mode 100644 index 4b9aa3ba2..000000000 --- a/crates/evm/core/src/abi/hardhat_console.rs +++ /dev/null @@ -1,566 +0,0 @@ -use alloy_primitives::Selector; -use alloy_sol_types::sol; -use foundry_macros::ConsoleFmt; -use once_cell::sync::Lazy; -use revm::primitives::HashMap; - -sol!( - #[sol(abi)] - #[derive(ConsoleFmt)] - HardhatConsole, - "src/abi/HardhatConsole.json" -); - -/// 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 Solc (and [`sol!`]) uses. -pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: Lazy> = Lazy::new(|| { - HashMap::from([ - // log(bool,uint256,uint256,address) - ([241, 97, 178, 33], [0, 221, 135, 185]), - // log(uint256,address,address,string) - ([121, 67, 220, 102], [3, 28, 111, 115]), - // log(uint256,bool,address,uint256) - ([65, 181, 239, 59], [7, 130, 135, 245]), - // log(bool,address,bool,uint256) - ([76, 182, 15, 209], [7, 131, 21, 2]), - // log(bool,uint256,address) - ([196, 210, 53, 7], [8, 142, 249, 210]), - // log(uint256,address,address,bool) - ([1, 85, 11, 4], [9, 31, 250, 245]), - // log(address,bool,uint256,string) - ([155, 88, 142, 204], [10, 166, 207, 173]), - // log(bool,bool,uint256,uint256) - ([70, 103, 222, 142], [11, 176, 14, 171]), - // log(bool,address,address,uint256) - ([82, 132, 189, 108], [12, 102, 209, 190]), - // log(uint256,address,uint256,uint256) - ([202, 154, 62, 180], [12, 156, 217, 193]), - // log(string,address,uint256) - ([7, 200, 18, 23], [13, 38, 185, 37]), - // log(address,string,uint256,bool) - ([126, 37, 13, 91], [14, 247, 224, 80]), - // log(address,uint256,address,uint256) - ([165, 217, 135, 104], [16, 15, 101, 14]), - // log(string,string,uint256,address) - ([93, 79, 70, 128], [16, 35, 247, 178]), - // log(bool,string,uint256) - ([192, 56, 42, 172], [16, 147, 238, 17]), - // log(bool,bool,uint256) - ([176, 19, 101, 187], [18, 242, 22, 2]), - // log(bool,address,uint256,address) - ([104, 241, 88, 181], [19, 107, 5, 221]), - // log(bool,uint256,address,uint256) - ([202, 165, 35, 106], [21, 55, 220, 135]), - // log(bool,string,uint256,address) - ([91, 34, 185, 56], [21, 150, 161, 206]), - // log(address,string,string,uint256) - ([161, 79, 208, 57], [21, 159, 137, 39]), - // log(uint256,address,uint256,address) - ([253, 178, 236, 212], [21, 193, 39, 181]), - // log(uint256,uint256,address,bool) - ([168, 232, 32, 174], [21, 202, 196, 118]), - // log(bool,string,bool,uint256) - ([141, 111, 156, 165], [22, 6, 163, 147]), - // log(address,address,uint256) - ([108, 54, 109, 114], [23, 254, 97, 133]), - // log(uint256,uint256,uint256,uint256) - ([92, 160, 173, 62], [25, 63, 184, 0]), - // log(bool,string,uint256,string) - ([119, 161, 171, 237], [26, 217, 109, 230]), - // log(bool,uint256,address,string) - ([24, 9, 19, 65], [27, 179, 176, 154]), - // log(string,uint256,address) - ([227, 132, 159, 121], [28, 126, 196, 72]), - // log(uint256,bool) - ([30, 109, 212, 236], [28, 157, 126, 179]), - // log(address,uint256,address,string) - ([93, 113, 243, 158], [29, 169, 134, 234]), - // log(address,string,uint256,uint256) - ([164, 201, 42, 96], [29, 200, 225, 184]), - // log(uint256,bool,uint256) - ([90, 77, 153, 34], [32, 9, 128, 20]), - // log(uint256,bool,bool) - ([213, 206, 172, 224], [32, 113, 134, 80]), - // log(address,uint256,uint256,address) - ([30, 246, 52, 52], [32, 227, 152, 77]), - // log(uint256,string,string,string) - ([87, 221, 10, 17], [33, 173, 6, 131]), - // log(address,uint256,bool,uint256) - ([105, 143, 67, 146], [34, 246, 185, 153]), - // log(uint256,address,address,address) - ([85, 71, 69, 249], [36, 136, 180, 20]), - // log(string,bool,string,uint256) - ([52, 203, 48, 141], [36, 249, 20, 101]), - // log(bool,uint256,address,address) - ([138, 47, 144, 170], [38, 245, 96, 168]), - // log(uint256,uint256,string,string) - ([124, 3, 42, 50], [39, 216, 175, 210]), - // log(bool,string,uint256,uint256) - ([142, 74, 232, 110], [40, 134, 63, 203]), - // log(uint256,bool,string,uint256) - ([145, 95, 219, 40], [44, 29, 7, 70]), - // log(address,uint256,uint256,uint256) - ([61, 14, 157, 228], [52, 240, 230, 54]), - // log(uint256,bool,address) - ([66, 78, 255, 191], [53, 8, 95, 123]), - // log(string,uint256,bool,bool) - ([227, 127, 243, 208], [53, 76, 54, 214]), - // log(bool,uint256,uint256) - ([59, 92, 3, 224], [55, 16, 51, 103]), - // log(bool,uint256,uint256,uint256) - ([50, 223, 165, 36], [55, 75, 180, 178]), - // log(uint256,string,uint256) - ([91, 109, 232, 63], [55, 170, 125, 76]), - // log(address,bool,uint256,uint256) - ([194, 16, 160, 30], [56, 111, 245, 244]), - // log(address,address,bool,uint256) - ([149, 214, 95, 17], [57, 113, 231, 140]), - // log(bool,uint256) - ([54, 75, 106, 146], [57, 145, 116, 211]), - // log(uint256,string,uint256,address) - ([171, 123, 217, 253], [59, 34, 121, 180]), - // log(address,uint256,bool,bool) - ([254, 161, 213, 90], [59, 245, 229, 55]), - // log(uint256,address,string,string) - ([141, 119, 134, 36], [62, 18, 140, 163]), - // log(string,address,bool,uint256) - ([197, 209, 187, 139], [62, 159, 134, 106]), - // log(uint256,uint256,string,address) - ([67, 50, 133, 162], [66, 210, 29, 183]), - // log(address,string,uint256,string) - ([93, 19, 101, 201], [68, 136, 48, 168]), - // log(uint256,bool,address,bool) - ([145, 251, 18, 66], [69, 77, 84, 165]), - // log(address,string,address,uint256) - ([140, 25, 51, 169], [69, 127, 227, 207]), - // log(uint256,address,string,uint256) - ([160, 196, 20, 232], [70, 130, 107, 93]), - // log(uint256,uint256,bool) - ([103, 87, 15, 247], [71, 102, 218, 114]), - // log(address,uint256,address,address) - ([236, 36, 132, 111], [71, 141, 28, 98]), - // log(address,uint256,uint256,string) - ([137, 52, 13, 171], [74, 40, 192, 23]), - // log(bool,bool,address,uint256) - ([96, 147, 134, 231], [76, 18, 61, 87]), - // log(uint256,string,bool) - ([70, 167, 208, 206], [76, 237, 167, 90]), - // log(string,uint256,address,uint256) - ([88, 73, 122, 254], [79, 4, 253, 198]), - // log(address,string,bool,uint256) - ([231, 32, 82, 28], [81, 94, 56, 182]), - // log(bool,address,uint256,string) - ([160, 104, 88, 51], [81, 240, 159, 248]), - // log(bool,bool,uint256,address) - ([11, 255, 149, 13], [84, 167, 169, 160]), - // log(uint256,uint256,address,address) - ([202, 147, 155, 32], [86, 165, 209, 177]), - // log(string,string,uint256) - ([243, 98, 202, 89], [88, 33, 239, 161]), - // log(string,uint256,string) - ([163, 245, 199, 57], [89, 112, 224, 137]), - // log(uint256,uint256,uint256,string) - ([120, 173, 122, 12], [89, 207, 203, 227]), - // log(string,address,uint256,string) - ([76, 85, 242, 52], [90, 71, 118, 50]), - // log(uint256,address,uint256) - ([136, 67, 67, 170], [90, 155, 94, 213]), - // log(string,uint256,string,string) - ([108, 152, 218, 226], [90, 184, 78, 31]), - // log(uint256,address,bool,uint256) - ([123, 8, 232, 235], [90, 189, 153, 42]), - // log(address,uint256,string,address) - ([220, 121, 38, 4], [92, 67, 13, 71]), - // log(uint256,uint256,address) - ([190, 51, 73, 27], [92, 150, 179, 49]), - // log(string,bool,address,uint256) - ([40, 223, 78, 150], [93, 8, 187, 5]), - // log(string,string,uint256,string) - ([141, 20, 44, 221], [93, 26, 151, 26]), - // log(uint256,uint256,string,uint256) - ([56, 148, 22, 61], [93, 162, 151, 235]), - // log(string,uint256,address,address) - ([234, 200, 146, 129], [94, 162, 183, 174]), - // log(uint256,address,uint256,bool) - ([25, 246, 115, 105], [95, 116, 58, 124]), - // log(bool,address,uint256) - ([235, 112, 75, 175], [95, 123, 154, 251]), - // log(uint256,string,address,address) - ([127, 165, 69, 139], [97, 104, 237, 97]), - // log(bool,bool,uint256,bool) - ([171, 92, 193, 196], [97, 158, 77, 14]), - // log(address,string,uint256,address) - ([223, 215, 216, 11], [99, 24, 54, 120]), - // log(uint256,address,string) - ([206, 131, 4, 123], [99, 203, 65, 249]), - // log(string,address,uint256,address) - ([163, 102, 236, 128], [99, 251, 139, 197]), - // log(uint256,string) - ([15, 163, 243, 69], [100, 63, 208, 223]), - // log(string,bool,uint256,uint256) - ([93, 191, 240, 56], [100, 181, 187, 103]), - // log(address,uint256,uint256,bool) - ([236, 75, 168, 162], [102, 241, 188, 103]), - // log(address,uint256,bool) - ([229, 74, 225, 68], [103, 130, 9, 168]), - // log(address,string,uint256) - ([28, 218, 242, 138], [103, 221, 111, 241]), - // log(uint256,bool,string,string) - ([164, 51, 252, 253], [104, 200, 184, 189]), - // log(uint256,string,uint256,bool) - ([135, 90, 110, 46], [105, 26, 143, 116]), - // log(uint256,address) - ([88, 235, 134, 12], [105, 39, 108, 134]), - // log(uint256,bool,bool,address) - ([83, 6, 34, 93], [105, 100, 11, 89]), - // log(bool,uint256,string,uint256) - ([65, 128, 1, 27], [106, 17, 153, 226]), - // log(bool,string,uint256,bool) - ([32, 187, 201, 175], [107, 14, 93, 83]), - // log(uint256,uint256,address,string) - ([214, 162, 209, 222], [108, 222, 64, 184]), - // log(bool,bool,bool,uint256) - ([194, 72, 131, 77], [109, 112, 69, 193]), - // log(uint256,uint256,string) - ([125, 105, 14, 230], [113, 208, 74, 242]), - // log(uint256,address,address,uint256) - ([154, 60, 191, 150], [115, 110, 251, 182]), - // log(string,bool,uint256,string) - ([66, 185, 162, 39], [116, 45, 110, 231]), - // log(uint256,bool,bool,uint256) - ([189, 37, 173, 89], [116, 100, 206, 35]), - // log(string,uint256,uint256,bool) - ([247, 60, 126, 61], [118, 38, 219, 146]), - // log(uint256,uint256,string,bool) - ([178, 46, 175, 6], [122, 246, 171, 37]), - // log(uint256,string,address) - ([31, 144, 242, 74], [122, 250, 201, 89]), - // log(address,uint256,address) - ([151, 236, 163, 148], [123, 192, 216, 72]), - // log(bool,string,string,uint256) - ([93, 219, 37, 146], [123, 224, 195, 235]), - // log(bool,address,uint256,uint256) - ([155, 254, 114, 188], [123, 241, 129, 161]), - // log(string,uint256,string,address) - ([187, 114, 53, 233], [124, 70, 50, 164]), - // log(string,string,address,uint256) - ([74, 129, 165, 106], [124, 195, 198, 7]), - // log(string,uint256,string,bool) - ([233, 159, 130, 207], [125, 36, 73, 29]), - // log(bool,bool,uint256,string) - ([80, 97, 137, 55], [125, 212, 208, 224]), - // log(bool,uint256,bool,uint256) - ([211, 222, 85, 147], [127, 155, 188, 162]), - // log(address,bool,string,uint256) - ([158, 18, 123, 110], [128, 230, 162, 11]), - // log(string,uint256,address,bool) - ([17, 6, 168, 247], [130, 17, 42, 66]), - // log(uint256,string,uint256,uint256) - ([192, 4, 56, 7], [130, 194, 91, 116]), - // log(address,uint256) - ([34, 67, 207, 163], [131, 9, 232, 168]), - // log(string,uint256,uint256,string) - ([165, 78, 212, 189], [133, 75, 52, 150]), - // log(uint256,bool,string) - ([139, 14, 20, 254], [133, 119, 80, 33]), - // log(address,uint256,string,string) - ([126, 86, 198, 147], [136, 168, 196, 6]), - // log(uint256,bool,uint256,address) - ([79, 64, 5, 142], [136, 203, 96, 65]), - // log(uint256,uint256,address,uint256) - ([97, 11, 168, 192], [136, 246, 228, 178]), - // log(string,bool,uint256,bool) - ([60, 197, 181, 211], [138, 247, 207, 138]), - // log(address,bool,bool,uint256) - ([207, 181, 135, 86], [140, 78, 93, 230]), - // log(address,address,uint256,address) - ([214, 198, 82, 118], [141, 166, 222, 245]), - // log(string,bool,bool,uint256) - ([128, 117, 49, 232], [142, 63, 120, 169]), - // log(bool,uint256,uint256,string) - ([218, 6, 102, 200], [142, 105, 251, 93]), - // log(string,string,string,uint256) - ([159, 208, 9, 245], [142, 175, 176, 43]), - // log(string,address,address,uint256) - ([110, 183, 148, 61], [142, 243, 243, 153]), - // log(uint256,string,address,bool) - ([249, 63, 255, 55], [144, 195, 10, 86]), - // log(uint256,address,bool,string) - ([99, 240, 226, 66], [144, 251, 6, 170]), - // log(bool,uint256,bool,string) - ([182, 213, 105, 212], [145, 67, 219, 177]), - // log(uint256,bool,uint256,bool) - ([210, 171, 196, 253], [145, 160, 46, 42]), - // log(string,address,string,uint256) - ([143, 98, 75, 233], [145, 209, 17, 46]), - // log(string,bool,uint256,address) - ([113, 211, 133, 13], [147, 94, 9, 191]), - // log(address,address,address,uint256) - ([237, 94, 172, 135], [148, 37, 13, 119]), - // log(uint256,uint256,bool,address) - ([225, 23, 116, 79], [154, 129, 106, 131]), - // log(bool,uint256,bool,address) - ([66, 103, 199, 248], [154, 205, 54, 22]), - // log(address,address,uint256,bool) - ([194, 246, 136, 236], [155, 66, 84, 226]), - // log(uint256,address,bool) - ([122, 208, 18, 142], [155, 110, 192, 66]), - // log(uint256,string,address,string) - ([248, 152, 87, 127], [156, 58, 223, 161]), - // log(address,bool,uint256) - ([44, 70, 141, 21], [156, 79, 153, 251]), - // log(uint256,address,string,address) - ([203, 229, 142, 253], [156, 186, 143, 255]), - // log(string,uint256,address,string) - ([50, 84, 194, 232], [159, 251, 47, 147]), - // log(address,uint256,address,bool) - ([241, 129, 161, 233], [161, 188, 201, 179]), - // log(uint256,bool,address,address) - ([134, 237, 193, 12], [161, 239, 76, 187]), - // log(address,uint256,string) - ([186, 249, 104, 73], [161, 242, 232, 170]), - // log(address,uint256,bool,address) - ([35, 229, 73, 114], [163, 27, 253, 204]), - // log(uint256,uint256,bool,string) - ([239, 217, 203, 238], [165, 180, 252, 153]), - // log(bool,string,address,uint256) - ([27, 11, 149, 91], [165, 202, 218, 148]), - // log(address,bool,address,uint256) - ([220, 113, 22, 210], [167, 92, 89, 222]), - // log(string,uint256,uint256,uint256) - ([8, 238, 86, 102], [167, 168, 120, 83]), - // log(uint256,uint256,bool,bool) - ([148, 190, 59, 177], [171, 8, 90, 230]), - // log(string,uint256,bool,string) - ([118, 204, 96, 100], [171, 247, 58, 152]), - // log(uint256,bool,address,string) - ([162, 48, 118, 30], [173, 224, 82, 199]), - // log(uint256,string,bool,address) - ([121, 111, 40, 160], [174, 46, 197, 129]), - // log(uint256,string,string,uint256) - ([118, 236, 99, 94], [176, 40, 201, 189]), - // log(uint256,string,string) - ([63, 87, 194, 149], [177, 21, 97, 31]), - // log(uint256,string,string,bool) - ([18, 134, 43, 152], [179, 166, 182, 189]), - // log(bool,uint256,address,bool) - ([101, 173, 244, 8], [180, 195, 20, 255]), - // log(string,uint256) - ([151, 16, 169, 208], [182, 14, 114, 204]), - // log(address,uint256,uint256) - ([135, 134, 19, 94], [182, 155, 202, 246]), - // log(uint256,bool,bool,bool) - ([78, 108, 83, 21], [182, 245, 119, 161]), - // log(uint256,string,uint256,string) - ([162, 188, 12, 153], [183, 185, 20, 202]), - // log(uint256,string,bool,bool) - ([81, 188, 43, 193], [186, 83, 93, 156]), - // log(uint256,address,address) - ([125, 119, 166, 27], [188, 253, 155, 224]), - // log(address,address,uint256,uint256) - ([84, 253, 243, 228], [190, 85, 52, 129]), - // log(bool,uint256,uint256,bool) - ([164, 29, 129, 222], [190, 152, 67, 83]), - // log(address,uint256,string,uint256) - ([245, 18, 207, 155], [191, 1, 248, 145]), - // log(bool,address,string,uint256) - ([11, 153, 252, 34], [194, 31, 100, 199]), - // log(string,string,uint256,bool) - ([230, 86, 88, 202], [195, 168, 166, 84]), - // log(bool,uint256,string) - ([200, 57, 126, 176], [195, 252, 57, 112]), - // log(address,bool,uint256,bool) - ([133, 205, 197, 175], [196, 100, 62, 32]), - // log(uint256,uint256,uint256,bool) - ([100, 82, 185, 203], [197, 152, 209, 133]), - // log(address,uint256,bool,string) - ([142, 142, 78, 117], [197, 173, 133, 249]), - // log(string,uint256,string,uint256) - ([160, 196, 178, 37], [198, 126, 169, 209]), - // log(uint256,bool,uint256,uint256) - ([86, 130, 141, 164], [198, 172, 199, 168]), - // log(string,bool,uint256) - ([41, 27, 185, 208], [201, 89, 88, 214]), - // log(string,uint256,uint256) - ([150, 156, 221, 3], [202, 71, 196, 235]), - // log(string,uint256,bool) - ([241, 2, 238, 5], [202, 119, 51, 177]), - // log(uint256,address,string,bool) - ([34, 164, 121, 166], [204, 50, 171, 7]), - // log(address,bool,uint256,address) - ([13, 140, 230, 30], [204, 247, 144, 161]), - // log(bool,uint256,bool,bool) - ([158, 1, 247, 65], [206, 181, 244, 215]), - // log(uint256,string,bool,uint256) - ([164, 180, 138, 127], [207, 0, 152, 128]), - // log(address,uint256,string,bool) - ([164, 2, 79, 17], [207, 24, 16, 92]), - // log(uint256,uint256,uint256) - ([231, 130, 10, 116], [209, 237, 122, 60]), - // log(uint256,string,bool,string) - ([141, 72, 156, 160], [210, 212, 35, 205]), - // log(uint256,string,string,address) - ([204, 152, 138, 160], [213, 131, 198, 2]), - // log(bool,address,uint256,bool) - ([238, 141, 134, 114], [214, 1, 159, 28]), - // log(string,string,bool,uint256) - ([134, 129, 138, 122], [214, 174, 250, 210]), - // log(uint256,address,uint256,string) - ([62, 211, 189, 40], [221, 176, 101, 33]), - // log(uint256,bool,bool,string) - ([49, 138, 229, 155], [221, 219, 149, 97]), - // log(uint256,bool,uint256,string) - ([232, 221, 188, 86], [222, 3, 231, 116]), - // log(string,uint256,bool,address) - ([229, 84, 157, 145], [224, 233, 91, 152]), - // log(string,uint256,uint256,address) - ([190, 215, 40, 191], [226, 29, 226, 120]), - // log(uint256,address,bool,bool) - ([126, 39, 65, 13], [227, 81, 20, 15]), - // log(bool,bool,string,uint256) - ([23, 139, 70, 133], [227, 169, 202, 47]), - // log(string,uint256,bool,uint256) - ([85, 14, 110, 245], [228, 27, 111, 111]), - // log(bool,uint256,string,bool) - ([145, 210, 248, 19], [229, 231, 11, 43]), - // log(uint256,string,address,uint256) - ([152, 231, 243, 243], [232, 211, 1, 141]), - // log(bool,uint256,bool) - ([27, 173, 201, 235], [232, 222, 251, 169]), - // log(uint256,uint256,bool,uint256) - ([108, 100, 124, 140], [235, 127, 111, 210]), - // log(uint256,bool,string,bool) - ([52, 110, 184, 199], [235, 146, 141, 127]), - // log(address,address,string,uint256) - ([4, 40, 147, 0], [239, 28, 239, 231]), - // log(uint256,bool,string,address) - ([73, 110, 43, 180], [239, 82, 144, 24]), - // log(uint256,address,bool,address) - ([182, 49, 48, 148], [239, 114, 197, 19]), - // log(string,string,uint256,uint256) - ([213, 207, 23, 208], [244, 93, 125, 44]), - // log(bool,uint256,string,string) - ([211, 42, 101, 72], [245, 188, 34, 73]), - // log(uint256,uint256) - ([108, 15, 105, 128], [246, 102, 113, 90]), - // log(uint256) and logUint(uint256) - ([245, 177, 187, 169], [248, 44, 80, 241]), - // log(string,address,uint256,uint256) - ([218, 163, 148, 189], [248, 245, 27, 30]), - // log(uint256,uint256,uint256,address) - ([224, 133, 63, 105], [250, 129, 133, 175]), - // log(string,address,uint256,bool) - ([90, 193, 193, 60], [252, 72, 69, 240]), - // log(address,address,uint256,string) - ([157, 209, 46, 173], [253, 180, 249, 144]), - // log(bool,uint256,string,address) - ([165, 199, 13, 41], [254, 221, 31, 255]), - // logInt(int256) - ([78, 12, 29, 29], [101, 37, 181, 245]), - // logBytes(bytes) - ([11, 231, 127, 86], [225, 123, 249, 86]), - // logBytes1(bytes1) - ([110, 24, 161, 40], [111, 65, 113, 201]), - // logBytes2(bytes2) - ([233, 182, 34, 150], [155, 94, 148, 62]), - // logBytes3(bytes3) - ([45, 131, 73, 38], [119, 130, 250, 45]), - // logBytes4(bytes4) - ([224, 95, 72, 209], [251, 163, 173, 57]), - // logBytes5(bytes5) - ([166, 132, 128, 141], [85, 131, 190, 46]), - // logBytes6(bytes6) - ([174, 132, 165, 145], [73, 66, 173, 198]), - // logBytes7(bytes7) - ([78, 213, 126, 40], [69, 116, 175, 171]), - // logBytes8(bytes8) - ([79, 132, 37, 46], [153, 2, 228, 127]), - // logBytes9(bytes9) - ([144, 189, 140, 208], [80, 161, 56, 223]), - // logBytes10(bytes10) - ([1, 61, 23, 139], [157, 194, 168, 151]), - // logBytes11(bytes11) - ([4, 0, 74, 46], [220, 8, 182, 167]), - // logBytes12(bytes12) - ([134, 160, 106, 189], [118, 86, 214, 199]), - // logBytes13(bytes13) - ([148, 82, 158, 52], [52, 193, 216, 27]), - // logBytes14(bytes14) - ([146, 102, 240, 127], [60, 234, 186, 101]), - // logBytes15(bytes15) - ([218, 149, 116, 224], [89, 26, 61, 162]), - // logBytes16(bytes16) - ([102, 92, 97, 4], [31, 141, 115, 18]), - // logBytes17(bytes17) - ([51, 159, 103, 58], [248, 154, 83, 47]), - // logBytes18(bytes18) - ([196, 210, 61, 154], [216, 101, 38, 66]), - // logBytes19(bytes19) - ([94, 107, 90, 51], [0, 245, 107, 201]), - // logBytes20(bytes20) - ([81, 136, 227, 233], [236, 184, 86, 126]), - // logBytes21(bytes21) - ([233, 218, 53, 96], [48, 82, 192, 143]), - // logBytes22(bytes22) - ([213, 250, 232, 156], [128, 122, 180, 52]), - // logBytes23(bytes23) - ([171, 161, 207, 13], [73, 121, 176, 55]), - // logBytes24(bytes24) - ([241, 179, 91, 52], [9, 119, 174, 252]), - // logBytes25(bytes25) - ([11, 132, 188, 88], [174, 169, 150, 63]), - // logBytes26(bytes26) - ([248, 177, 73, 241], [211, 99, 86, 40]), - // logBytes27(bytes27) - ([58, 55, 87, 221], [252, 55, 47, 159]), - // logBytes28(bytes28) - ([200, 42, 234, 238], [56, 47, 154, 52]), - // logBytes29(bytes29) - ([75, 105, 195, 213], [122, 24, 118, 65]), - // logBytes30(bytes30) - ([238, 18, 196, 237], [196, 52, 14, 246]), - // logBytes31(bytes31) - ([194, 133, 77, 146], [129, 252, 134, 72]), - // logBytes32(bytes32) - ([39, 183, 207, 133], [45, 33, 214, 247]), - ]) -}); - -#[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/core/src/abi/mod.rs b/crates/evm/core/src/abi/mod.rs deleted file mode 100644 index 54f35c966..000000000 --- a/crates/evm/core/src/abi/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Several ABI-related utilities for executors. - -pub use foundry_cheatcodes_spec::Vm; - -mod console; -pub use console::{format_units_int, format_units_uint, Console}; - -mod hardhat_console; -pub use hardhat_console::{ - hh_console_selector, patch_hh_console_selector, HardhatConsole, - HARDHAT_CONSOLE_SELECTOR_PATCHES, -}; diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index fa6604153..d493f9d6e 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -1,9 +1,9 @@ //! A wrapper around `Backend` that is clone-on-write used for fuzzing. +use super::{BackendError, ForkInfo}; use crate::{ backend::{ - diagnostic::RevertDiagnostic, error::DatabaseError, Backend, DatabaseExt, LocalForkId, - RevertSnapshotAction, + diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertSnapshotAction, }, fork::{CreateFork, ForkId}, InspectorExt, @@ -11,6 +11,7 @@ use crate::{ use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; use eyre::WrapErr; +use foundry_fork_db::DatabaseError; use revm::{ db::DatabaseRef, primitives::{ @@ -24,8 +25,6 @@ use std::{ collections::{BTreeMap, HashMap}, }; -use super::ForkInfo; - /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. /// /// Any changes made during its existence that affect the caching layer of the underlying Database @@ -96,6 +95,10 @@ impl<'a> CowBackend<'a> { Ok(res) } + pub fn new_borrowed(backend: &'a Backend) -> Self { + Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } + } + /// Returns whether there was a snapshot failure in the backend. /// /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. @@ -200,13 +203,13 @@ impl<'a> DatabaseExt for CowBackend<'a> { self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) } - fn transact>( + fn transact( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { self.backend_mut(env).transact(id, transaction, env, journaled_state, inspector) } @@ -239,7 +242,7 @@ impl<'a> DatabaseExt for CowBackend<'a> { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError> { + ) -> Result<(), BackendError> { self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) } @@ -271,6 +274,10 @@ impl<'a> DatabaseExt for CowBackend<'a> { self.backend.has_cheatcode_access(account) } + fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { + self.backend.to_mut().set_blockhash(block_number, block_hash); + } + fn get_test_contract_address(&self) -> Option

{ self.backend.get_test_contract_address() } @@ -291,7 +298,7 @@ impl<'a> DatabaseRef for CowBackend<'a> { DatabaseRef::storage_ref(self.backend.as_ref(), address, index) } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { DatabaseRef::block_hash_ref(self.backend.as_ref(), number) } } @@ -311,7 +318,7 @@ impl<'a> Database for CowBackend<'a> { DatabaseRef::storage_ref(self, address, index) } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { DatabaseRef::block_hash_ref(self, number) } } diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index aedabb0bd..50b2bb934 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -1,5 +1,6 @@ use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; +pub use foundry_fork_db::{DatabaseError, DatabaseResult}; use futures::channel::mpsc::{SendError, TrySendError}; use revm::primitives::EVMError; use std::{ @@ -7,17 +8,18 @@ use std::{ sync::{mpsc::RecvError, Arc}, }; -/// Result alias with `DatabaseError` as error -pub type DatabaseResult = Result; +pub type BackendResult = Result; /// Errors that can happen when working with [`revm::Database`] #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] -pub enum DatabaseError { +pub enum BackendError { #[error("{0}")] Message(String), #[error("cheatcodes are not enabled for {0}; see `vm.allowCheatcodes(address)`")] NoCheats(Address), + #[error(transparent)] + Database(#[from] DatabaseError), #[error("failed to fetch account info for {0}")] MissingAccount(Address), #[error("missing bytecode for code hash {0}")] @@ -53,7 +55,7 @@ pub enum DatabaseError { Other(String), } -impl DatabaseError { +impl BackendError { /// Create a new error with a message pub fn msg(msg: impl Into) -> Self { Self::Message(msg.into()) @@ -67,6 +69,7 @@ impl DatabaseError { fn get_rpc_error(&self) -> Option<&eyre::Error> { match self { Self::GetAccount(_, err) => Some(err), + Self::Database(_) => None, // TODO: Revisit this case Self::GetStorage(_, _, err) => Some(err), Self::GetBlockHash(_, err) => Some(err), Self::GetFullBlock(_, err) => Some(err), @@ -97,30 +100,33 @@ impl DatabaseError { } } -impl From for DatabaseError { +impl From for BackendError { fn from(value: tokio::task::JoinError) -> Self { Self::display(value) } } -impl From> for DatabaseError { +impl From> for BackendError { fn from(value: TrySendError) -> Self { value.into_send_error().into() } } -impl From for DatabaseError { +impl From for BackendError { fn from(value: Infallible) -> Self { match value {} } } // Note: this is mostly necessary to use some revm internals that return an [EVMError] -impl From> for DatabaseError { - fn from(err: EVMError) -> Self { +impl> From> for BackendError { + fn from(err: EVMError) -> Self { match err { - EVMError::Database(err) => err, - err => Self::Other(err.to_string()), + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::msg(err), + EVMError::Header(err) => Self::msg(err.to_string()), + EVMError::Precompile(err) => Self::msg(err), + EVMError::Transaction(err) => Self::msg(err.to_string()), } } } diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index e8f371f61..e819c5313 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -1,7 +1,8 @@ //! In-memory database. -use crate::{backend::error::DatabaseError, snapshot::Snapshots}; +use crate::snapshot::Snapshots; use alloy_primitives::{Address, B256, U256}; +use foundry_fork_db::DatabaseError; use revm::{ db::{CacheDB, DatabaseRef, EmptyDB}, primitives::{Account, AccountInfo, Bytecode, HashMap as Map}, @@ -43,7 +44,7 @@ impl DatabaseRef for MemDb { DatabaseRef::storage_ref(&self.inner, address, index) } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { DatabaseRef::block_hash_ref(&self.inner, number) } } @@ -64,7 +65,7 @@ impl Database for MemDb { Database::storage(&mut self.inner, address, index) } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { Database::block_hash(&mut self.inner, number) } } @@ -110,7 +111,7 @@ impl DatabaseRef for EmptyDBWrapper { Ok(self.0.storage_ref(address, index)?) } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { Ok(self.0.block_hash_ref(number)?) } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index e552909f4..36f184dc2 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -2,17 +2,18 @@ use crate::{ constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, - fork::{CreateFork, ForkId, MultiFork, SharedBackend}, + fork::{CreateFork, ForkId, MultiFork}, snapshot::Snapshots, utils::configure_tx_env, InspectorExt, }; use alloy_genesis::GenesisAccount; -use alloy_primitives::{b256, keccak256, Address, B256, U256}; +use alloy_primitives::{keccak256, uint, Address, B256, U256}; use alloy_rpc_types::{Block, BlockNumberOrTag, BlockTransactions, Transaction}; use alloy_serde::WithOtherFields; use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; +pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; use foundry_zksync_core::{ convert::ConvertH160, ACCOUNT_CODE_STORAGE_ADDRESS, L2_BASE_TOKEN_ADDRESS, NONCE_HOLDER_ADDRESS, }; @@ -23,7 +24,7 @@ use revm::{ precompile::{PrecompileSpecId, Precompiles}, primitives::{ Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, EvmState, EvmStorageSlot, - HashMap as Map, Log, ResultAndState, SpecId, TransactTo, KECCAK_EMPTY, + HashMap as Map, Log, ResultAndState, SpecId, TxKind, KECCAK_EMPTY, }, Database, DatabaseCommit, JournaledState, }; @@ -36,7 +37,7 @@ mod diagnostic; pub use diagnostic::RevertDiagnostic; mod error; -pub use error::{DatabaseError, DatabaseResult}; +pub use error::{BackendError, BackendResult, DatabaseError, DatabaseResult}; mod cow; pub use cow::CowBackend; @@ -66,10 +67,12 @@ type ForkLookupIndex = usize; const DEFAULT_PERSISTENT_ACCOUNTS: [Address; 3] = [CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, CALLER]; -/// Slot corresponding to "failed" in bytes on the cheatcodes (HEVM) address. -/// Not prefixed with 0x. -const GLOBAL_FAILURE_SLOT: B256 = - b256!("6661696c65640000000000000000000000000000000000000000000000000000"); +/// `bytes32("failed")`, as a storage slot key into [`CHEATCODE_ADDRESS`]. +/// +/// Used by all `forge-std` test contracts and newer `DSTest` test contracts as a global marker for +/// a failed test. +pub const GLOBAL_FAIL_SLOT: U256 = + uint!(0x6661696c65640000000000000000000000000000000000000000000000000000_U256); /// Defines the info of a fork pub struct ForkInfo { @@ -81,14 +84,7 @@ pub struct ForkInfo { /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] -pub trait DatabaseExt: Database { - /// Retrieves information about a fork - /// - /// The fork must already exist defined by the provided [LocalForkId]. - /// If exists, we return the information about the fork, namely it's type (ZK or EVM) - /// and the the fork environment. - fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result; - +pub trait DatabaseExt: Database + DatabaseCommit { /// Creates a new snapshot at the current point of execution. /// /// A snapshot is associated with a new unique id that's created for the snapshot. @@ -96,6 +92,13 @@ pub trait DatabaseExt: Database { /// [RevertSnapshotAction], it will keep the snapshot alive or delete it. fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; + /// Retrieves information about a fork + /// + /// The fork must already exist defined by the provided [LocalForkId]. + /// If exists, we return the information about the fork, namely it's type (ZK or EVM) + /// and the the fork environment. + fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result; + /// Reverts the snapshot if it exists /// /// Returns `true` if the snapshot was successfully reverted, `false` if no snapshot for that id @@ -212,16 +215,14 @@ pub trait DatabaseExt: Database { ) -> eyre::Result<()>; /// Fetches the given transaction for the fork and executes it, committing the state in the DB - fn transact>( + fn transact( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, - ) -> eyre::Result<()> - where - Self: Sized; + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on fn active_fork_id(&self) -> Option; @@ -288,7 +289,7 @@ pub trait DatabaseExt: Database { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError>; + ) -> Result<(), BackendError>; /// Returns true if the given account is currently marked as persistent. fn is_persistent(&self, acc: &Address) -> bool; @@ -302,7 +303,8 @@ pub trait DatabaseExt: Database { /// Marks the given account as persistent. fn add_persistent_account(&mut self, account: Address) -> bool; - /// Removes persistent status from all given accounts + /// Removes persistent status from all given accounts. + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) where Self: Sized, @@ -313,6 +315,7 @@ pub trait DatabaseExt: Database { } /// Extends the persistent accounts with the accounts the iterator yields. + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) where Self: Sized, @@ -338,16 +341,16 @@ pub trait DatabaseExt: Database { /// Ensures that `account` is allowed to execute cheatcodes /// /// Returns an error if [`Self::has_cheatcode_access`] returns `false` - fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), BackendError> { if !self.has_cheatcode_access(account) { - return Err(DatabaseError::NoCheats(*account)); + return Err(BackendError::NoCheats(*account)); } Ok(()) } /// Same as [`Self::ensure_cheatcode_access()`] but only enforces it if the backend is currently /// in forking mode - fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), BackendError> { if self.is_forked_mode() { return self.ensure_cheatcode_access(account); } @@ -356,6 +359,22 @@ pub trait DatabaseExt: Database { /// Retrieves test contract's address fn get_test_contract_address(&self) -> Option
; + + /// Set the blockhash for a given block number. + /// + /// # Arguments + /// + /// * `number` - The block number to set the blockhash for + /// * `hash` - The blockhash to set + /// + /// # Note + /// + /// This function mimics the EVM limits of the `blockhash` operation: + /// - It sets the blockhash for blocks where `block.number - 256 <= number < block.number` + /// - Setting a blockhash for the current block (number == block.number) has no effect + /// - Setting a blockhash for future blocks (number > block.number) has no effect + /// - Setting a blockhash for blocks older than `block.number - 256` has no effect + fn set_blockhash(&mut self, block_number: U256, block_hash: B256); } struct _ObjectSafe(dyn DatabaseExt); @@ -413,6 +432,7 @@ struct _ObjectSafe(dyn DatabaseExt); /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. #[derive(Clone, Debug)] +#[must_use] pub struct Backend { /// The access point for managing forks forks: MultiFork, @@ -443,7 +463,6 @@ pub struct Backend { inner: BackendInner, /// Keeps track of the fork type fork_url_type: CachedForkType, - /// TODO: Ensure this parameter is updated on `select_fork`. /// /// Keeps track if the backend is in ZK mode. @@ -455,14 +474,19 @@ pub struct Backend { impl Backend { /// Creates a new Backend with a spawned multi fork thread. + /// + /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory + /// database. pub fn spawn(fork: Option) -> Self { Self::new(MultiFork::spawn(), fork) } /// Creates a new instance of `Backend` /// - /// if `fork` is `Some` this will launch with a `fork` database, otherwise with an in-memory - /// database + /// If `fork` is `Some` this will use a `fork` database, otherwise with an in-memory + /// database. + /// + /// Prefer using [`spawn`](Self::spawn) instead. pub fn new(forks: MultiFork, fork: Option) -> Self { trace!(target: "backend", forking_mode=?fork.is_some(), "creating executor backend"); // Note: this will take of registering the `fork` @@ -573,7 +597,6 @@ impl Backend { /// This will also grant cheatcode access to the test account pub fn set_test_contract(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting test account"); - self.add_persistent_account(acc); self.allow_cheatcode_access(acc); self.inner.test_contract_address = Some(acc); @@ -619,69 +642,6 @@ impl Backend { self.inner.has_snapshot_failure = has_snapshot_failure } - /// Checks if the test contract associated with this backend failed, See - /// [Self::is_failed_test_contract] - pub fn is_failed(&self) -> bool { - self.has_snapshot_failure() || - self.test_contract_address() - .map(|addr| self.is_failed_test_contract(addr)) - .unwrap_or_default() - } - - /// Checks if the given test function failed - /// - /// DSTest will not revert inside its `assertEq`-like functions which allows - /// to test multiple assertions in 1 test function while also preserving logs. - /// Instead, it stores whether an `assert` failed in a boolean variable that we can read - pub fn is_failed_test_contract(&self, address: Address) -> bool { - /* - contract DSTest { - bool public IS_TEST = true; - // slot 0 offset 1 => second byte of slot0 - bool private _failed; - } - */ - let value = self.storage_ref(address, U256::ZERO).unwrap_or_default(); - value.as_le_bytes()[1] != 0 - } - - /// Checks if the given test function failed by looking at the present value of the test - /// contract's `JournaledState` - /// - /// See [`Self::is_failed_test_contract()]` - /// - /// Note: we assume the test contract is either `forge-std/Test` or `DSTest` - pub fn is_failed_test_contract_state( - &self, - address: Address, - current_state: &JournaledState, - ) -> bool { - if let Some(account) = current_state.state.get(&address) { - let value = account - .storage - .get(&revm::primitives::U256::ZERO) - .cloned() - .unwrap_or_default() - .present_value(); - return value.as_le_bytes()[1] != 0; - } - - false - } - - /// In addition to the `_failed` variable, `DSTest::fail()` stores a failure - /// in "failed" - /// See - pub fn is_global_failure(&self, current_state: &JournaledState) -> bool { - if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) { - let slot: U256 = GLOBAL_FAILURE_SLOT.into(); - let value = account.storage.get(&slot).cloned().unwrap_or_default().present_value(); - return value == revm::primitives::U256::from(1); - } - - false - } - /// When creating or switching forks, we update the AccountInfo of the contract pub(crate) fn update_fork_db( &self, @@ -689,11 +649,6 @@ impl Backend { target_fork: &mut Fork, merge_zk_db: bool, ) { - debug_assert!( - self.inner.test_contract_address.is_some(), - "Test contract address must be set" - ); - self.update_fork_db_contracts( self.inner.persistent_accounts.iter().copied(), active_journaled_state, @@ -818,8 +773,8 @@ impl Backend { self.set_spec_id(env.handler_cfg.spec_id); let test_contract = match env.tx.transact_to { - TransactTo::Call(to) => to, - TransactTo::Create => { + TxKind::Call(to) => to, + TxKind::Create => { let nonce = self .basic_ref(env.tx.caller) .map(|b| b.unwrap_or_default().nonce) @@ -839,6 +794,7 @@ impl Backend { /// /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. + #[instrument(name = "inspect", level = "debug", skip_all)] pub fn inspect<'a, I: InspectorExt<&'a mut Self>>( &'a mut self, env: &mut EnvWithHandlerCfg, @@ -887,7 +843,7 @@ impl Backend { /// This account data then would not match the account data of a fork if it exists. /// So when the first fork is initialized we replace these accounts with the actual account as /// it exists on the fork. - fn prepare_init_journal_state(&mut self) -> Result<(), DatabaseError> { + fn prepare_init_journal_state(&mut self) -> Result<(), BackendError> { let loaded_accounts = self .fork_init_journaled_state .state @@ -915,7 +871,7 @@ impl Backend { // otherwise we need to replace the account's info with the one from the fork's // database let fork_account = Database::basic(&mut fork.db, loaded_account)? - .ok_or(DatabaseError::MissingAccount(loaded_account))?; + .ok_or(BackendError::MissingAccount(loaded_account))?; init_account.info = fork_account; } fork.journaled_state = journaled_state; @@ -943,10 +899,8 @@ impl Backend { } else { let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; - let number = block - .header - .number - .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumberOrTag::Latest.into()))?; + let number = + block.header.number.ok_or_else(|| BackendError::msg("missing block number"))?; Ok((number, block)) } @@ -1044,10 +998,17 @@ impl DatabaseExt for Backend { if action.is_keep() { self.inner.snapshots.insert_at(snapshot.clone(), id); } - // need to check whether there's a global failure which means an error occurred either - // during the snapshot or even before - if self.is_global_failure(current_state) { - self.set_snapshot_failure(true); + + // https://github.com/foundry-rs/foundry/issues/3055 + // Check if an error occurred either during or before the snapshot. + // DSTest contracts don't have snapshot functionality, so this slot is enough to check + // for failure here. + if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) { + if let Some(slot) = account.storage.get(&GLOBAL_FAIL_SLOT) { + if !slot.present_value.is_zero() { + self.set_snapshot_failure(true); + } + } } // merge additional logs @@ -1146,6 +1107,16 @@ impl DatabaseExt for Backend { return Ok(()); } + // Update block number and timestamp of active fork (if any) with current env values, + // in order to preserve values changed by using `roll` and `warp` cheatcodes. + if let Some(active_fork_id) = self.active_fork_id() { + self.forks.update_block( + self.ensure_fork_id(active_fork_id).cloned()?, + env.block.number, + env.block.timestamp, + )?; + } + let fork_id = self.ensure_fork_id(id).cloned()?; let idx = self.inner.ensure_fork_index(&fork_id)?; @@ -1183,7 +1154,7 @@ impl DatabaseExt for Backend { // Initialize caller with its fork info if let Some(mut acc) = caller_account { let fork_account = Database::basic(&mut target_fork.db, caller)? - .ok_or(DatabaseError::MissingAccount(caller))?; + .ok_or(BackendError::MissingAccount(caller))?; acc.info = fork_account; target_fork.journaled_state.state.insert(caller, acc); @@ -1238,7 +1209,7 @@ impl DatabaseExt for Backend { } self.active_fork_ids = Some((id, idx)); - // update the environment accordingly + // Update current environment with environment of newly selected fork. update_current_env_with_fork_env(env, fork_env); Ok(()) @@ -1336,13 +1307,13 @@ impl DatabaseExt for Backend { Ok(()) } - fn transact>( + fn transact( &mut self, maybe_id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1451,7 +1422,7 @@ impl DatabaseExt for Backend { &mut self, allocs: &BTreeMap, journaled_state: &mut JournaledState, - ) -> Result<(), DatabaseError> { + ) -> Result<(), BackendError> { // Loop through all of the allocs defined in the map and commit them to the journal. for (addr, acc) in allocs.iter() { // Fetch the account from the journaled state. Will create a new account if it does @@ -1531,6 +1502,14 @@ impl DatabaseExt for Backend { fn get_test_contract_address(&self) -> Option
{ self.test_contract_address() } + + fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { + if let Some(db) = self.active_fork_db_mut() { + db.block_hashes.insert(block_number, block_hash); + } else { + self.mem_db.block_hashes.insert(block_number, block_hash); + } + } } impl DatabaseRef for Backend { @@ -1560,7 +1539,7 @@ impl DatabaseRef for Backend { } } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { if let Some(db) = self.active_fork_db() { db.block_hash_ref(number) } else { @@ -1583,7 +1562,7 @@ impl Database for Backend { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { if let Some(db) = self.active_fork_db_mut() { - db.basic(address) + Ok(db.basic(address)?) } else { Ok(self.mem_db.basic(address)?) } @@ -1591,7 +1570,7 @@ impl Database for Backend { fn code_by_hash(&mut self, code_hash: B256) -> Result { if let Some(db) = self.active_fork_db_mut() { - db.code_by_hash(code_hash) + Ok(db.code_by_hash(code_hash)?) } else { Ok(self.mem_db.code_by_hash(code_hash)?) } @@ -1599,15 +1578,15 @@ impl Database for Backend { fn storage(&mut self, address: Address, index: U256) -> Result { if let Some(db) = self.active_fork_db_mut() { - Database::storage(db, address, index) + Ok(Database::storage(db, address, index)?) } else { Ok(Database::storage(&mut self.mem_db, address, index)?) } } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { if let Some(db) = self.active_fork_db_mut() { - db.block_hash(number) + Ok(db.block_hash(number)?) } else { Ok(self.mem_db.block_hash(number)?) } @@ -2049,10 +2028,13 @@ fn commit_transaction>( let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); + let depth = journaled_state.depth; let db = Backend::new_with_fork(fork_id, fork, journaled_state); - crate::utils::new_evm_with_inspector(db, env, inspector) - .transact() - .wrap_err("backend: failed committing transaction")? + + let mut evm = crate::utils::new_evm_with_inspector(db, env, inspector); + // Adjust inner EVM depth to ensure that inspectors receive accurate data. + evm.context.evm.inner.journaled_state.depth = depth + 1; + evm.transact().wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); @@ -2086,7 +2068,7 @@ fn apply_state_changeset( journaled_state: &mut JournaledState, fork: &mut Fork, persistent_accounts: &HashSet
, -) -> Result<(), DatabaseError> { +) -> Result<(), BackendError> { // commit the state and update the loaded accounts fork.db.commit(state); @@ -2095,3 +2077,65 @@ fn apply_state_changeset( Ok(()) } + +#[cfg(test)] +mod tests { + use crate::{backend::Backend, fork::CreateFork, opts::EvmOpts}; + use alloy_primitives::{Address, U256}; + use alloy_provider::Provider; + use foundry_common::provider::get_http_provider; + use foundry_config::{Config, NamedChain}; + use foundry_fork_db::cache::{BlockchainDb, BlockchainDbMeta}; + use revm::DatabaseRef; + + const ENDPOINT: Option<&str> = option_env!("ETH_RPC_URL"); + + #[tokio::test(flavor = "multi_thread")] + async fn can_read_write_cache() { + let Some(endpoint) = ENDPOINT else { return }; + + let provider = get_http_provider(endpoint); + + let block_num = provider.get_block_number().await.unwrap(); + + let config = Config::figment(); + let mut evm_opts = config.extract::().unwrap(); + evm_opts.fork_block_number = Some(block_num); + + let (env, _block) = evm_opts.fork_evm_env(endpoint).await.unwrap(); + + let fork = CreateFork { + enable_caching: true, + url: endpoint.to_string(), + env: env.clone(), + evm_opts, + }; + + let backend = Backend::spawn(Some(fork)); + + // some rng contract from etherscan + let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); + + let idx = U256::from(0u64); + let _value = backend.storage_ref(address, idx); + let _account = backend.basic_ref(address); + + // fill some slots + let num_slots = 10u64; + for idx in 1..num_slots { + let _ = backend.storage_ref(address, U256::from(idx)); + } + drop(backend); + + let meta = + BlockchainDbMeta { cfg_env: env.cfg, block_env: env.block, hosts: Default::default() }; + + let db = BlockchainDb::new( + meta, + Some(Config::foundry_block_cache_dir(NamedChain::Mainnet, block_num).unwrap()), + ); + assert!(db.accounts().read().contains_key(&address)); + assert!(db.storage().read().contains_key(&address)); + assert_eq!(db.storage().read().get(&address).unwrap().len(), num_slots as usize); + } +} diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index 0ae1b6475..713d03d87 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -1,7 +1,5 @@ use alloy_primitives::{address, b256, hex, Address, B256}; -pub use foundry_common::HARDHAT_CONSOLE_ADDRESS; - /// The cheatcode handler address. /// /// This is the same address as the one used in DappTools's HEVM. @@ -34,9 +32,22 @@ pub const MAGIC_ASSUME: &[u8] = b"FOUNDRY::ASSUME"; /// Magic return value returned by the `skip` cheatcode. pub const MAGIC_SKIP: &[u8] = b"FOUNDRY::SKIP"; +/// The address that deploys the default CREATE2 deployer contract. +pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = + address!("3fAB184622Dc19b6109349B94811493BF2a45362"); /// The default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER: Address = address!("4e59b44847b379578588920ca78fbf26c0b4956c"); /// The initcode of the default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); /// The runtime code of the default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE: &[u8] = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create2_deployer() { + assert_eq!(DEFAULT_CREATE2_DEPLOYER_DEPLOYER.create(0), DEFAULT_CREATE2_DEPLOYER); + } +} diff --git a/crates/evm/core/src/debug.rs b/crates/evm/core/src/debug.rs deleted file mode 100644 index 21705f128..000000000 --- a/crates/evm/core/src/debug.rs +++ /dev/null @@ -1,242 +0,0 @@ -use crate::opcodes; -use alloy_primitives::{Address, Bytes, U256}; -use arrayvec::ArrayVec; -use revm::interpreter::OpCode; -use revm_inspectors::tracing::types::CallKind; -use serde::{Deserialize, Serialize}; - -/// An arena of [DebugNode]s -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DebugArena { - /// The arena of nodes - pub arena: Vec, -} - -impl Default for DebugArena { - fn default() -> Self { - Self::new() - } -} - -impl DebugArena { - /// Creates a new debug arena. - pub const fn new() -> Self { - Self { arena: Vec::new() } - } - - /// Pushes a new debug node into the arena - pub fn push_node(&mut self, mut new_node: DebugNode) -> usize { - fn recursively_push( - arena: &mut Vec, - entry: usize, - mut new_node: DebugNode, - ) -> usize { - match new_node.depth { - // We found the parent node, add the new node as a child - _ if arena[entry].depth == new_node.depth - 1 => { - let id = arena.len(); - new_node.location = arena[entry].children.len(); - new_node.parent = Some(entry); - arena[entry].children.push(id); - arena.push(new_node); - id - } - // We haven't found the parent node, go deeper - _ => { - let child = *arena[entry].children.last().expect("Disconnected debug node"); - recursively_push(arena, child, new_node) - } - } - } - - if self.arena.is_empty() { - // This is the initial node at depth 0, so we just insert it. - self.arena.push(new_node); - 0 - } else if new_node.depth == 0 { - // This is another node at depth 0, for example instructions between calls. We insert - // it as a child of the original root node. - let id = self.arena.len(); - new_node.location = self.arena[0].children.len(); - new_node.parent = Some(0); - self.arena[0].children.push(id); - self.arena.push(new_node); - id - } else { - // We try to find the parent of this node recursively - recursively_push(&mut self.arena, 0, new_node) - } - } - - /// Recursively traverses the tree of debug nodes and flattens it into a [Vec] where each - /// item contains: - /// - /// - The address of the contract being executed - /// - A [Vec] of debug steps along that contract's execution path - /// - An enum denoting the type of call this is - /// - /// This makes it easy to pretty print the execution steps. - pub fn flatten(&self, entry: usize) -> Vec { - let mut flattened = Vec::new(); - self.flatten_to(entry, &mut flattened); - flattened - } - - /// Recursively traverses the tree of debug nodes and flattens it into the given list. - /// - /// See [`flatten`](Self::flatten) for more information. - pub fn flatten_to(&self, entry: usize, out: &mut Vec) { - let node = &self.arena[entry]; - - if !node.steps.is_empty() { - out.push(node.flat()); - } - - for child in &node.children { - self.flatten_to(*child, out); - } - } -} - -/// A node in the arena. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct DebugNode { - /// Parent node index in the arena. - pub parent: Option, - /// Children node indexes in the arena. - pub children: Vec, - /// Location in parent. - pub location: usize, - /// Execution context. - /// - /// Note that this is the address of the *code*, not necessarily the address of the storage. - pub address: Address, - /// The kind of call this is. - pub kind: CallKind, - /// Depth of the call. - pub depth: usize, - /// The debug steps. - pub steps: Vec, -} - -impl From for DebugNodeFlat { - #[inline] - fn from(node: DebugNode) -> Self { - node.into_flat() - } -} - -impl From<&DebugNode> for DebugNodeFlat { - #[inline] - fn from(node: &DebugNode) -> Self { - node.flat() - } -} - -impl DebugNode { - /// Creates a new debug node. - pub fn new(address: Address, depth: usize, steps: Vec) -> Self { - Self { address, depth, steps, ..Default::default() } - } - - /// Flattens this node into a [`DebugNodeFlat`]. - pub fn flat(&self) -> DebugNodeFlat { - DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps.clone() } - } - - /// Flattens this node into a [`DebugNodeFlat`]. - pub fn into_flat(self) -> DebugNodeFlat { - DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps } - } -} - -/// Flattened [`DebugNode`] from an arena. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct DebugNodeFlat { - /// Execution context. - /// - /// Note that this is the address of the *code*, not necessarily the address of the storage. - pub address: Address, - /// The kind of call this is. - pub kind: CallKind, - /// The debug steps. - pub steps: Vec, -} - -impl DebugNodeFlat { - /// Creates a new debug node flat. - pub fn new(address: Address, kind: CallKind, steps: Vec) -> Self { - Self { address, kind, steps } - } -} - -/// A `DebugStep` is a snapshot of the EVM's runtime state. -/// -/// It holds the current program counter (where in the program you are), -/// the stack and memory (prior to the opcodes execution), any bytes to be -/// pushed onto the stack, and the instruction counter for use with sourcemap. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DebugStep { - /// Stack *prior* to running the associated opcode - pub stack: Vec, - /// Memory *prior* to running the associated opcode - pub memory: Bytes, - /// Calldata *prior* to running the associated opcode - pub calldata: Bytes, - /// Returndata *prior* to running the associated opcode - pub returndata: Bytes, - /// Opcode to be executed - pub instruction: u8, - /// Optional bytes that are being pushed onto the stack. - /// Empty if the opcode is not a push or PUSH0. - #[serde(serialize_with = "hex::serialize", deserialize_with = "deserialize_arrayvec_hex")] - pub push_bytes: ArrayVec, - /// The program counter at this step. - /// - /// Note: To map this step onto source code using a source map, you must convert the program - /// counter to an instruction counter. - pub pc: usize, - /// Cumulative gas usage - pub total_gas_used: u64, -} - -impl Default for DebugStep { - fn default() -> Self { - Self { - stack: vec![], - memory: Default::default(), - calldata: Default::default(), - returndata: Default::default(), - instruction: revm::interpreter::opcode::INVALID, - push_bytes: Default::default(), - pc: 0, - total_gas_used: 0, - } - } -} - -impl DebugStep { - /// Pretty print the step's opcode - pub fn pretty_opcode(&self) -> String { - let instruction = OpCode::new(self.instruction).map_or("INVALID", |op| op.as_str()); - if !self.push_bytes.is_empty() { - format!("{instruction}(0x{})", hex::encode(&self.push_bytes)) - } else { - instruction.to_string() - } - } - - /// Returns `true` if the opcode modifies memory. - pub fn opcode_modifies_memory(&self) -> bool { - OpCode::new(self.instruction).map_or(false, opcodes::modifies_memory) - } -} - -fn deserialize_arrayvec_hex<'de, D: serde::Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - let bytes: Vec = hex::deserialize(deserializer)?; - let mut array = ArrayVec::new(); - array.try_extend_from_slice(&bytes).map_err(serde::de::Error::custom)?; - Ok(array) -} diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index ae44791e4..cc736477f 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -2,10 +2,11 @@ use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Error, JsonAbi}; -use alloy_primitives::{Log, Selector}; +use alloy_primitives::{hex, Log, Selector}; use alloy_sol_types::{SolCall, SolError, SolEventInterface, SolInterface, SolValue}; use foundry_cheatcodes_spec::Vm; -use foundry_common::{Console, SELECTOR_LEN}; +use foundry_common::SELECTOR_LEN; +use foundry_evm_abi::Console; use itertools::Itertools; use revm::interpreter::InstructionResult; use rustc_hash::FxHashMap; diff --git a/crates/evm/core/src/fork/backend.rs b/crates/evm/core/src/fork/backend.rs deleted file mode 100644 index 2ec2b4489..000000000 --- a/crates/evm/core/src/fork/backend.rs +++ /dev/null @@ -1,877 +0,0 @@ -//! Smart caching and deduplication of requests when using a forking provider -use crate::{ - backend::{DatabaseError, DatabaseResult}, - fork::{cache::FlushJsonBlockCacheDB, BlockchainDb}, -}; -use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; -use alloy_provider::{network::AnyNetwork, Provider}; -use alloy_rpc_types::{Block, BlockId, Transaction}; -use alloy_serde::WithOtherFields; -use alloy_transport::Transport; -use eyre::WrapErr; -use foundry_common::NON_ARCHIVE_NODE_WARNING; -use futures::{ - channel::mpsc::{channel, Receiver, Sender}, - stream::Stream, - task::{Context, Poll}, - Future, FutureExt, -}; -use revm::{ - db::DatabaseRef, - primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, -}; -use rustc_hash::FxHashMap; -use std::{ - collections::{hash_map::Entry, HashMap, VecDeque}, - future::IntoFuture, - marker::PhantomData, - pin::Pin, - sync::{ - mpsc::{channel as oneshot_channel, Sender as OneshotSender}, - Arc, - }, -}; - -// Various future/request type aliases - -type AccountFuture = - Pin, Address)> + Send>>; -type StorageFuture = Pin, Address, U256)> + Send>>; -type BlockHashFuture = Pin, u64)> + Send>>; -type FullBlockFuture = - Pin, Err>, BlockId)> + Send>>; -type TransactionFuture = Pin< - Box< - dyn Future, Err>, B256)> - + Send, - >, ->; -type BytecodeHashFuture = - Pin, Err>, B256)> + Send>>; - -type AccountInfoSender = OneshotSender>; -type StorageSender = OneshotSender>; -type BlockHashSender = OneshotSender>; -type FullBlockSender = OneshotSender>; -type TransactionSender = OneshotSender>>; -type ByteCodeHashSender = OneshotSender>; - -/// Request variants that are executed by the provider -enum ProviderRequest { - Account(AccountFuture), - Storage(StorageFuture), - BlockHash(BlockHashFuture), - FullBlock(FullBlockFuture), - Transaction(TransactionFuture), - ByteCodeHash(BytecodeHashFuture), -} - -/// The Request type the Backend listens for -#[derive(Debug)] -enum BackendRequest { - /// Fetch the account info - Basic(Address, AccountInfoSender), - /// Fetch a storage slot - Storage(Address, U256, StorageSender), - /// Fetch a block hash - BlockHash(u64, BlockHashSender), - /// Fetch an entire block with transactions - FullBlock(BlockId, FullBlockSender), - /// Fetch a transaction - Transaction(B256, TransactionSender), - /// Sets the pinned block to fetch data from - SetPinnedBlock(BlockId), - /// Get the bytecode for the given hash - ByteCodeHash(B256, ByteCodeHashSender), -} - -/// Handles an internal provider and listens for requests. -/// -/// This handler will remain active as long as it is reachable (request channel still open) and -/// requests are in progress. -#[must_use = "futures do nothing unless polled"] -pub struct BackendHandler { - provider: P, - transport: PhantomData, - /// Stores all the data. - db: BlockchainDb, - /// Requests currently in progress - pending_requests: Vec>, - /// Listeners that wait for a `get_account` related response - account_requests: HashMap>, - /// Listeners that wait for a `get_storage_at` response - storage_requests: HashMap<(Address, U256), Vec>, - /// Listeners that wait for a `get_block` response - block_requests: FxHashMap>, - /// Incoming commands. - incoming: Receiver, - /// unprocessed queued requests - queued_requests: VecDeque, - /// The block to fetch data from. - // This is an `Option` so that we can have less code churn in the functions below - block_id: Option, -} - -pub trait ZkSyncMiddleware: Send + Sync { - fn get_bytecode_by_hash( - &self, - hash: B256, - ) -> impl std::future::Future>> - + std::marker::Send; -} - -impl BackendHandler -where - T: Transport + Clone, - P: ZkSyncMiddleware + Provider + Clone + Unpin + 'static, -{ - fn new( - provider: P, - db: BlockchainDb, - rx: Receiver, - block_id: Option, - ) -> Self { - Self { - provider, - db, - pending_requests: Default::default(), - account_requests: Default::default(), - storage_requests: Default::default(), - block_requests: Default::default(), - queued_requests: Default::default(), - incoming: rx, - block_id, - transport: PhantomData, - } - } - - /// handle the request in queue in the future. - /// - /// We always check: - /// 1. if the requested value is already stored in the cache, then answer the sender - /// 2. otherwise, fetch it via the provider but check if a request for that value is already in - /// progress (e.g. another Sender just requested the same account) - fn on_request(&mut self, req: BackendRequest) { - match req { - BackendRequest::Basic(addr, sender) => { - trace!(target: "backendhandler", "received request basic address={:?}", addr); - let acc = self.db.accounts().read().get(&addr).cloned(); - if let Some(basic) = acc { - let _ = sender.send(Ok(basic)); - } else { - self.request_account(addr, sender); - } - } - BackendRequest::BlockHash(number, sender) => { - let hash = self.db.block_hashes().read().get(&U256::from(number)).cloned(); - if let Some(hash) = hash { - let _ = sender.send(Ok(hash)); - } else { - self.request_hash(number, sender); - } - } - BackendRequest::FullBlock(number, sender) => { - self.request_full_block(number, sender); - } - BackendRequest::Transaction(tx, sender) => { - self.request_transaction(tx, sender); - } - BackendRequest::Storage(addr, idx, sender) => { - // account is already stored in the cache - let value = - self.db.storage().read().get(&addr).and_then(|acc| acc.get(&idx).copied()); - if let Some(value) = value { - let _ = sender.send(Ok(value)); - } else { - // account present but not storage -> fetch storage - self.request_account_storage(addr, idx, sender); - } - } - BackendRequest::SetPinnedBlock(block_id) => { - self.block_id = Some(block_id); - } - BackendRequest::ByteCodeHash(code_hash, sender) => { - self.request_bytecode_by_hash(code_hash, sender); - } - } - } - - /// process a request for account's storage - fn request_account_storage(&mut self, address: Address, idx: U256, listener: StorageSender) { - match self.storage_requests.entry((address, idx)) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - trace!(target: "backendhandler", %address, %idx, "preparing storage request"); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let block_id = self.block_id.unwrap_or_default(); - let fut = Box::pin(async move { - let storage = provider - .get_storage_at(address, idx) - .block_id(block_id) - .await - .map_err(Into::into); - (storage, address, idx) - }); - self.pending_requests.push(ProviderRequest::Storage(fut)); - } - } - } - - /// returns the future that fetches the account data - fn get_account_req(&self, address: Address) -> ProviderRequest { - trace!(target: "backendhandler", "preparing account request, address={:?}", address); - let provider = self.provider.clone(); - let block_id = self.block_id.unwrap_or_default(); - let fut = Box::pin(async move { - let balance = provider.get_balance(address).block_id(block_id).into_future(); - let nonce = provider.get_transaction_count(address).block_id(block_id).into_future(); - let code = provider.get_code_at(address).block_id(block_id).into_future(); - let resp = tokio::try_join!(balance, nonce, code).map_err(Into::into); - (resp, address) - }); - ProviderRequest::Account(fut) - } - - /// process a request for an account - fn request_account(&mut self, address: Address, listener: AccountInfoSender) { - match self.account_requests.entry(address) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - entry.insert(vec![listener]); - self.pending_requests.push(self.get_account_req(address)); - } - } - } - - /// process a request for an entire block - fn request_full_block(&mut self, number: BlockId, sender: FullBlockSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_block(number, true.into()) - .await - .wrap_err("could not fetch block {number:?}"); - (sender, block, number) - }); - - self.pending_requests.push(ProviderRequest::FullBlock(fut)); - } - - /// process a request for a transactions - fn request_transaction(&mut self, tx: B256, sender: TransactionSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_transaction_by_hash(tx) - .await - .wrap_err_with(|| format!("could not get transaction {tx}")) - .and_then(|maybe| { - maybe.ok_or_else(|| eyre::eyre!("could not get transaction {tx}")) - }); - (sender, block, tx) - }); - - self.pending_requests.push(ProviderRequest::Transaction(fut)); - } - - /// process a request for a block hash - fn request_hash(&mut self, number: u64, listener: BlockHashSender) { - match self.block_requests.entry(number) { - Entry::Occupied(mut entry) => { - entry.get_mut().push(listener); - } - Entry::Vacant(entry) => { - trace!(target: "backendhandler", number, "preparing block hash request"); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider - .get_block_by_number(number.into(), false) - .await - .wrap_err("failed to get block"); - - let block_hash = match block { - Ok(Some(block)) => Ok(block - .header - .hash - .expect("empty block hash on mined block, this should never happen")), - Ok(None) => { - warn!(target: "backendhandler", ?number, "block not found"); - // if no block was returned then the block does not exist, in which case - // we return empty hash - Ok(KECCAK_EMPTY) - } - Err(err) => { - error!(target: "backendhandler", %err, ?number, "failed to get block"); - Err(err) - } - }; - (block_hash, number) - }); - self.pending_requests.push(ProviderRequest::BlockHash(fut)); - } - } - } - - fn request_bytecode_by_hash(&mut self, code_hash: B256, sender: ByteCodeHashSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let bytecode = provider - .get_bytecode_by_hash(code_hash) - .await - .wrap_err("could not get bytecode {code_hash}"); - (sender, bytecode, code_hash) - }); - - self.pending_requests.push(ProviderRequest::ByteCodeHash(fut)); - } -} - -impl Future for BackendHandler -where - T: Transport + Clone + Unpin, - P: ZkSyncMiddleware + Provider + Clone + Unpin + 'static, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let pin = self.get_mut(); - loop { - // Drain queued requests first. - while let Some(req) = pin.queued_requests.pop_front() { - pin.on_request(req) - } - - // receive new requests to delegate to the underlying provider - loop { - match Pin::new(&mut pin.incoming).poll_next(cx) { - Poll::Ready(Some(req)) => { - pin.queued_requests.push_back(req); - } - Poll::Ready(None) => { - trace!(target: "backendhandler", "last sender dropped, ready to drop (&flush cache)"); - return Poll::Ready(()); - } - Poll::Pending => break, - } - } - - // poll all requests in progress - for n in (0..pin.pending_requests.len()).rev() { - let mut request = pin.pending_requests.swap_remove(n); - match &mut request { - ProviderRequest::Account(fut) => { - if let Poll::Ready((resp, addr)) = fut.poll_unpin(cx) { - // get the response - let (balance, nonce, code) = match resp { - Ok(res) => res, - Err(err) => { - let err = Arc::new(err); - if let Some(listeners) = pin.account_requests.remove(&addr) { - listeners.into_iter().for_each(|l| { - let _ = l.send(Err(DatabaseError::GetAccount( - addr, - Arc::clone(&err), - ))); - }) - } - continue; - } - }; - - // convert it to revm-style types - let (code, code_hash) = if !code.is_empty() { - (code.clone(), keccak256(&code)) - } else { - (Bytes::default(), KECCAK_EMPTY) - }; - - // update the cache - let acc = AccountInfo { - nonce, - balance, - code: Some(Bytecode::new_raw(code)), - code_hash, - }; - pin.db.accounts().write().insert(addr, acc.clone()); - - // notify all listeners - if let Some(listeners) = pin.account_requests.remove(&addr) { - listeners.into_iter().for_each(|l| { - let _ = l.send(Ok(acc.clone())); - }) - } - continue; - } - } - ProviderRequest::Storage(fut) => { - if let Poll::Ready((resp, addr, idx)) = fut.poll_unpin(cx) { - let value = match resp { - Ok(value) => value, - Err(err) => { - // notify all listeners - let err = Arc::new(err); - if let Some(listeners) = - pin.storage_requests.remove(&(addr, idx)) - { - listeners.into_iter().for_each(|l| { - let _ = l.send(Err(DatabaseError::GetStorage( - addr, - idx, - Arc::clone(&err), - ))); - }) - } - continue; - } - }; - - // update the cache - pin.db.storage().write().entry(addr).or_default().insert(idx, value); - - // notify all listeners - if let Some(listeners) = pin.storage_requests.remove(&(addr, idx)) { - listeners.into_iter().for_each(|l| { - let _ = l.send(Ok(value)); - }) - } - continue; - } - } - ProviderRequest::BlockHash(fut) => { - if let Poll::Ready((block_hash, number)) = fut.poll_unpin(cx) { - let value = match block_hash { - Ok(value) => value, - Err(err) => { - let err = Arc::new(err); - // notify all listeners - if let Some(listeners) = pin.block_requests.remove(&number) { - listeners.into_iter().for_each(|l| { - let _ = l.send(Err(DatabaseError::GetBlockHash( - number, - Arc::clone(&err), - ))); - }) - } - continue; - } - }; - - // update the cache - pin.db.block_hashes().write().insert(U256::from(number), value); - - // notify all listeners - if let Some(listeners) = pin.block_requests.remove(&number) { - listeners.into_iter().for_each(|l| { - let _ = l.send(Ok(value)); - }) - } - continue; - } - } - ProviderRequest::FullBlock(fut) => { - if let Poll::Ready((sender, resp, number)) = fut.poll_unpin(cx) { - let msg = match resp { - Ok(Some(block)) => Ok(block), - Ok(None) => Err(DatabaseError::BlockNotFound(number)), - Err(err) => { - let err = Arc::new(err); - Err(DatabaseError::GetFullBlock(number, err)) - } - }; - let _ = sender.send(msg); - continue; - } - } - ProviderRequest::Transaction(fut) => { - if let Poll::Ready((sender, tx, tx_hash)) = fut.poll_unpin(cx) { - let msg = match tx { - Ok(tx) => Ok(tx), - Err(err) => { - let err = Arc::new(err); - Err(DatabaseError::GetTransaction(tx_hash, err)) - } - }; - let _ = sender.send(msg); - continue; - } - } - ProviderRequest::ByteCodeHash(fut) => { - if let Poll::Ready((sender, bytecode, code_hash)) = fut.poll_unpin(cx) { - let msg = match bytecode { - Ok(Some(bytecode)) => Ok(bytecode), - Ok(None) => Err(DatabaseError::MissingCode(code_hash)), - Err(err) => { - let err = Arc::new(err); - Err(DatabaseError::GetBytecode(code_hash, err)) - } - }; - let _ = sender.send(msg); - continue - } - } - } - // not ready, insert and poll again - pin.pending_requests.push(request); - } - - // If no new requests have been queued, break to - // be polled again later. - if pin.queued_requests.is_empty() { - return Poll::Pending; - } - } - } -} - -/// A cloneable backend type that shares access to the backend data with all its clones. -/// -/// This backend type is connected to the `BackendHandler` via a mpsc channel. The `BackendHandler` -/// is spawned on a tokio task and listens for incoming commands on the receiver half of the -/// channel. A `SharedBackend` holds a sender for that channel, which is `Clone`, so there can be -/// multiple `SharedBackend`s communicating with the same `BackendHandler`, hence this `Backend` -/// type is thread safe. -/// -/// All `Backend` trait functions are delegated as a `BackendRequest` via the channel to the -/// `BackendHandler`. All `BackendRequest` variants include a sender half of an additional channel -/// that is used by the `BackendHandler` to send the result of an executed `BackendRequest` back to -/// `SharedBackend`. -/// -/// The `BackendHandler` holds a `Provider` to look up missing accounts or storage slots -/// from remote (e.g. infura). It detects duplicate requests from multiple `SharedBackend`s and -/// bundles them together, so that always only one provider request is executed. For example, there -/// are two `SharedBackend`s, `A` and `B`, both request the basic account info of account -/// `0xasd9sa7d...` at the same time. After the `BackendHandler` receives the request from `A`, it -/// sends a new provider request to the provider's endpoint, then it reads the identical request -/// from `B` and simply adds it as an additional listener for the request already in progress, -/// instead of sending another one. So that after the provider returns the response all listeners -/// (`A` and `B`) get notified. -// **Note**: the implementation makes use of [tokio::task::block_in_place()] when interacting with -// the underlying [BackendHandler] which runs on a separate spawned tokio task. -// [tokio::task::block_in_place()] -// > Runs the provided blocking function on the current thread without blocking the executor. -// This prevents issues (hangs) we ran into were the [SharedBackend] itself is called from a spawned -// task. -#[derive(Clone, Debug)] -pub struct SharedBackend { - /// channel used for sending commands related to database operations - backend: Sender, - /// Ensures that the underlying cache gets flushed once the last `SharedBackend` is dropped. - /// - /// There is only one instance of the type, so as soon as the last `SharedBackend` is deleted, - /// `FlushJsonBlockCacheDB` is also deleted and the cache is flushed. - cache: Arc, -} - -impl SharedBackend { - /// _Spawns_ a new `BackendHandler` on a `tokio::task` that listens for requests from any - /// `SharedBackend`. Missing values get inserted in the `db`. - /// - /// The spawned `BackendHandler` finishes once the last `SharedBackend` connected to it is - /// dropped. - /// - /// NOTE: this should be called with `Arc` - pub async fn spawn_backend( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> Self - where - T: Transport + Clone + Unpin, - P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, - { - let (shared, handler) = Self::new(provider, db, pin_block); - // spawn the provider handler to a task - trace!(target: "backendhandler", "spawning Backendhandler task"); - tokio::spawn(handler); - shared - } - - /// Same as `Self::spawn_backend` but spawns the `BackendHandler` on a separate `std::thread` in - /// its own `tokio::Runtime` - pub fn spawn_backend_thread( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> Self - where - T: Transport + Clone + Unpin, - P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, - { - let (shared, handler) = Self::new(provider, db, pin_block); - - // spawn a light-weight thread with a thread-local async runtime just for - // sending and receiving data from the remote client - std::thread::Builder::new() - .name("fork-backend".into()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to build tokio runtime"); - - rt.block_on(handler); - }) - .expect("failed to spawn thread"); - trace!(target: "backendhandler", "spawned Backendhandler thread"); - - shared - } - - /// Returns a new `SharedBackend` and the `BackendHandler` - pub fn new( - provider: P, - db: BlockchainDb, - pin_block: Option, - ) -> (Self, BackendHandler) - where - T: Transport + Clone + Unpin, - P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, - { - let (backend, backend_rx) = channel(1); - let cache = Arc::new(FlushJsonBlockCacheDB(Arc::clone(db.cache()))); - let handler = BackendHandler::new(provider, db, backend_rx, pin_block); - (Self { backend, cache }, handler) - } - - /// Updates the pinned block to fetch data from - pub fn set_pinned_block(&self, block: impl Into) -> eyre::Result<()> { - let req = BackendRequest::SetPinnedBlock(block.into()); - self.backend.clone().try_send(req).map_err(|e| eyre::eyre!("{:?}", e)) - } - - /// Returns the full block for the given block identifier - pub fn get_full_block(&self, block: impl Into) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::FullBlock(block.into(), sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - /// Returns the transaction for the hash - pub fn get_transaction(&self, tx: B256) -> DatabaseResult> { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Transaction(tx, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - fn do_get_basic(&self, address: Address) -> DatabaseResult> { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Basic(address, sender); - self.backend.clone().try_send(req)?; - rx.recv()?.map(Some) - }) - } - - fn do_get_storage(&self, address: Address, index: U256) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::Storage(address, index, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - fn do_get_block_hash(&self, number: u64) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::BlockHash(number, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - fn do_get_bytecode(&self, hash: B256) -> DatabaseResult { - tokio::task::block_in_place(|| { - let (sender, rx) = oneshot_channel(); - let req = BackendRequest::ByteCodeHash(hash, sender); - self.backend.clone().try_send(req)?; - rx.recv()? - }) - } - - /// Flushes the DB to disk if caching is enabled - pub(crate) fn flush_cache(&self) { - self.cache.0.flush(); - } -} - -impl DatabaseRef for SharedBackend { - type Error = DatabaseError; - - fn basic_ref(&self, address: Address) -> Result, Self::Error> { - trace!(target: "sharedbackend", %address, "request basic"); - self.do_get_basic(address).map_err(|err| { - error!(target: "sharedbackend", %err, %address, "Failed to send/recv `basic`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } - - fn code_by_hash_ref(&self, hash: B256) -> Result { - trace!(target: "sharedbackend", %hash, "request codehash"); - self.do_get_bytecode(hash).map_err(|err| { - error!(target: "sharedbackend", %err, %hash, "Failed to send/recv `code_by_hash`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } - - fn storage_ref(&self, address: Address, index: U256) -> Result { - trace!(target: "sharedbackend", "request storage {:?} at {:?}", address, index); - self.do_get_storage(address, index).map_err(|err| { - error!(target: "sharedbackend", %err, %address, %index, "Failed to send/recv `storage`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } - - fn block_hash_ref(&self, number: U256) -> Result { - if number > U256::from(u64::MAX) { - return Ok(KECCAK_EMPTY); - } - let number: U256 = number; - let number = number.to(); - trace!(target: "sharedbackend", "request block hash for number {:?}", number); - self.do_get_block_hash(number).map_err(|err| { - error!(target: "sharedbackend", %err, %number, "Failed to send/recv `block_hash`"); - if err.is_possibly_non_archive_node_error() { - error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); - } - err - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - backend::Backend, - fork::{BlockchainDbMeta, CreateFork, JsonBlockCacheDB}, - opts::EvmOpts, - }; - use foundry_common::provider::get_http_provider; - use foundry_config::{Config, NamedChain}; - use std::{collections::BTreeSet, path::PathBuf}; - - const ENDPOINT: Option<&str> = option_env!("ETH_RPC_URL"); - - #[tokio::test(flavor = "multi_thread")] - async fn shared_backend() { - let Some(endpoint) = ENDPOINT else { return }; - - let provider = get_http_provider(endpoint); - let meta = BlockchainDbMeta { - cfg_env: Default::default(), - block_env: Default::default(), - hosts: BTreeSet::from([endpoint.to_string()]), - }; - - let db = BlockchainDb::new(meta, None); - let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await; - - // some rng contract from etherscan - let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = U256::from(0u64); - let value = backend.storage_ref(address, idx).unwrap(); - let account = backend.basic_ref(address).unwrap().unwrap(); - - let mem_acc = db.accounts().read().get(&address).unwrap().clone(); - assert_eq!(account.balance, mem_acc.balance); - assert_eq!(account.nonce, mem_acc.nonce); - let slots = db.storage().read().get(&address).unwrap().clone(); - assert_eq!(slots.len(), 1); - assert_eq!(slots.get(&idx).copied().unwrap(), value); - - let num = U256::from(10u64); - let hash = backend.block_hash_ref(num).unwrap(); - let mem_hash = *db.block_hashes().read().get(&num).unwrap(); - assert_eq!(hash, mem_hash); - - let max_slots = 5; - let handle = std::thread::spawn(move || { - for i in 1..max_slots { - let idx = U256::from(i); - let _ = backend.storage_ref(address, idx); - } - }); - handle.join().unwrap(); - let slots = db.storage().read().get(&address).unwrap().clone(); - assert_eq!(slots.len() as u64, max_slots); - } - - #[test] - fn can_read_cache() { - let cache_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/storage.json"); - let json = JsonBlockCacheDB::load(cache_path).unwrap(); - assert!(!json.db().accounts.read().is_empty()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn can_read_write_cache() { - let Some(endpoint) = ENDPOINT else { return }; - - let provider = get_http_provider(endpoint); - - let block_num = provider.get_block_number().await.unwrap(); - - let config = Config::figment(); - let mut evm_opts = config.extract::().unwrap(); - evm_opts.fork_block_number = Some(block_num); - - let (env, _block) = evm_opts.fork_evm_env(endpoint).await.unwrap(); - - let fork = CreateFork { - enable_caching: true, - url: endpoint.to_string(), - env: env.clone(), - evm_opts, - }; - - let backend = Backend::spawn(Some(fork)); - - // some rng contract from etherscan - let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = U256::from(0u64); - let _value = backend.storage_ref(address, idx); - let _account = backend.basic_ref(address); - - // fill some slots - let num_slots = 10u64; - for idx in 1..num_slots { - let _ = backend.storage_ref(address, U256::from(idx)); - } - drop(backend); - - let meta = - BlockchainDbMeta { cfg_env: env.cfg, block_env: env.block, hosts: Default::default() }; - - let db = BlockchainDb::new( - meta, - Some(Config::foundry_block_cache_dir(NamedChain::Mainnet, block_num).unwrap()), - ); - assert!(db.accounts().read().contains_key(&address)); - assert!(db.storage().read().contains_key(&address)); - assert_eq!(db.storage().read().get(&address).unwrap().len(), num_slots as usize); - } -} diff --git a/crates/evm/core/src/fork/cache.rs b/crates/evm/core/src/fork/cache.rs deleted file mode 100644 index 9aea93585..000000000 --- a/crates/evm/core/src/fork/cache.rs +++ /dev/null @@ -1,620 +0,0 @@ -//! Cache related abstraction -use crate::backend::StateSnapshot; -use alloy_primitives::{Address, B256, U256}; -use parking_lot::RwLock; -use revm::{ - primitives::{Account, AccountInfo, AccountStatus, HashMap as Map, KECCAK_EMPTY}, - DatabaseCommit, -}; -use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; -use std::{ - collections::BTreeSet, - fs, - io::{BufWriter, Write}, - path::PathBuf, - sync::Arc, -}; -use url::Url; - -pub type StorageInfo = Map; - -/// A shareable Block database -#[derive(Clone, Debug)] -pub struct BlockchainDb { - /// Contains all the data - db: Arc, - /// metadata of the current config - meta: Arc>, - /// the cache that can be flushed - cache: Arc, -} - -impl BlockchainDb { - /// Creates a new instance of the [BlockchainDb]. - /// - /// If a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData] - /// and will try to use the cached entries it holds. - /// - /// This will return a new and empty [MemDb] if - /// - `cache_path` is `None` - /// - the file the `cache_path` points to, does not exist - /// - the file contains malformed data, or if it couldn't be read - /// - the provided `meta` differs from [BlockchainDbMeta] that's stored on disk - pub fn new(meta: BlockchainDbMeta, cache_path: Option) -> Self { - Self::new_db(meta, cache_path, false) - } - - /// Creates a new instance of the [BlockchainDb] and skips check when comparing meta - /// This is useful for offline-start mode when we don't want to fetch metadata of `block`. - /// - /// if a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData] - /// and will try to use the cached entries it holds. - /// - /// This will return a new and empty [MemDb] if - /// - `cache_path` is `None` - /// - the file the `cache_path` points to, does not exist - /// - the file contains malformed data, or if it couldn't be read - /// - the provided `meta` differs from [BlockchainDbMeta] that's stored on disk - pub fn new_skip_check(meta: BlockchainDbMeta, cache_path: Option) -> Self { - Self::new_db(meta, cache_path, true) - } - - fn new_db(meta: BlockchainDbMeta, cache_path: Option, skip_check: bool) -> Self { - trace!(target: "forge::cache", cache=?cache_path, "initialising blockchain db"); - // read cache and check if metadata matches - let cache = cache_path - .as_ref() - .and_then(|p| { - JsonBlockCacheDB::load(p).ok().filter(|cache| { - if skip_check { - return true - } - let mut existing = cache.meta().write(); - existing.hosts.extend(meta.hosts.clone()); - if meta != *existing { - warn!(target: "cache", "non-matching block metadata"); - false - } else { - true - } - }) - }) - .unwrap_or_else(|| JsonBlockCacheDB::new(Arc::new(RwLock::new(meta)), cache_path)); - - Self { db: Arc::clone(cache.db()), meta: Arc::clone(cache.meta()), cache: Arc::new(cache) } - } - - /// Returns the map that holds the account related info - pub fn accounts(&self) -> &RwLock> { - &self.db.accounts - } - - /// Returns the map that holds the storage related info - pub fn storage(&self) -> &RwLock> { - &self.db.storage - } - - /// Returns the map that holds all the block hashes - pub fn block_hashes(&self) -> &RwLock> { - &self.db.block_hashes - } - - /// Returns the Env related metadata - pub fn meta(&self) -> &Arc> { - &self.meta - } - - /// Returns the inner cache - pub fn cache(&self) -> &Arc { - &self.cache - } - - /// Returns the underlying storage - pub fn db(&self) -> &Arc { - &self.db - } -} - -/// relevant identifying markers in the context of [BlockchainDb] -#[derive(Clone, Debug, Eq, Serialize)] -pub struct BlockchainDbMeta { - pub cfg_env: revm::primitives::CfgEnv, - pub block_env: revm::primitives::BlockEnv, - /// all the hosts used to connect to - pub hosts: BTreeSet, -} - -impl BlockchainDbMeta { - /// Creates a new instance - pub fn new(env: revm::primitives::Env, url: String) -> Self { - let host = Url::parse(&url) - .ok() - .and_then(|url| url.host().map(|host| host.to_string())) - .unwrap_or(url); - - Self { cfg_env: env.cfg.clone(), block_env: env.block, hosts: BTreeSet::from([host]) } - } -} - -// ignore hosts to not invalidate the cache when different endpoints are used, as it's commonly the -// case for http vs ws endpoints -impl PartialEq for BlockchainDbMeta { - fn eq(&self, other: &Self) -> bool { - self.cfg_env == other.cfg_env && self.block_env == other.block_env - } -} - -impl<'de> Deserialize<'de> for BlockchainDbMeta { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - /// A backwards compatible representation of [revm::primitives::CfgEnv] - /// - /// This prevents deserialization errors of cache files caused by breaking changes to the - /// default [revm::primitives::CfgEnv], for example enabling an optional feature. - /// By hand rolling deserialize impl we can prevent cache file issues - struct CfgEnvBackwardsCompat { - inner: revm::primitives::CfgEnv, - } - - impl<'de> Deserialize<'de> for CfgEnvBackwardsCompat { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut value = serde_json::Value::deserialize(deserializer)?; - - // we check for breaking changes here - if let Some(obj) = value.as_object_mut() { - let default_value = - serde_json::to_value(revm::primitives::CfgEnv::default()).unwrap(); - for (key, value) in default_value.as_object().unwrap() { - if !obj.contains_key(key) { - obj.insert(key.to_string(), value.clone()); - } - } - } - - let cfg_env: revm::primitives::CfgEnv = - serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Ok(Self { inner: cfg_env }) - } - } - - /// A backwards compatible representation of [revm::primitives::BlockEnv] - /// - /// This prevents deserialization errors of cache files caused by breaking changes to the - /// default [revm::primitives::BlockEnv], for example enabling an optional feature. - /// By hand rolling deserialize impl we can prevent cache file issues - struct BlockEnvBackwardsCompat { - inner: revm::primitives::BlockEnv, - } - - impl<'de> Deserialize<'de> for BlockEnvBackwardsCompat { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut value = serde_json::Value::deserialize(deserializer)?; - - // we check for any missing fields here - if let Some(obj) = value.as_object_mut() { - let default_value = - serde_json::to_value(revm::primitives::BlockEnv::default()).unwrap(); - for (key, value) in default_value.as_object().unwrap() { - if !obj.contains_key(key) { - obj.insert(key.to_string(), value.clone()); - } - } - } - - let cfg_env: revm::primitives::BlockEnv = - serde_json::from_value(value).map_err(serde::de::Error::custom)?; - Ok(Self { inner: cfg_env }) - } - } - - // custom deserialize impl to not break existing cache files - #[derive(Deserialize)] - struct Meta { - cfg_env: CfgEnvBackwardsCompat, - block_env: BlockEnvBackwardsCompat, - /// all the hosts used to connect to - #[serde(alias = "host")] - hosts: Hosts, - } - - #[derive(Deserialize)] - #[serde(untagged)] - enum Hosts { - Multi(BTreeSet), - Single(String), - } - - let Meta { cfg_env, block_env, hosts } = Meta::deserialize(deserializer)?; - Ok(Self { - cfg_env: cfg_env.inner, - block_env: block_env.inner, - hosts: match hosts { - Hosts::Multi(hosts) => hosts, - Hosts::Single(host) => BTreeSet::from([host]), - }, - }) - } -} - -/// In Memory cache containing all fetched accounts and storage slots -/// and their values from RPC -#[derive(Debug, Default)] -pub struct MemDb { - /// Account related data - pub accounts: RwLock>, - /// Storage related data - pub storage: RwLock>, - /// All retrieved block hashes - pub block_hashes: RwLock>, -} - -impl MemDb { - /// Clears all data stored in this db - pub fn clear(&self) { - self.accounts.write().clear(); - self.storage.write().clear(); - self.block_hashes.write().clear(); - } - - // Inserts the account, replacing it if it exists already - pub fn do_insert_account(&self, address: Address, account: AccountInfo) { - self.accounts.write().insert(address, account); - } - - /// The implementation of [DatabaseCommit::commit()] - pub fn do_commit(&self, changes: Map) { - let mut storage = self.storage.write(); - let mut accounts = self.accounts.write(); - for (add, mut acc) in changes { - if acc.is_empty() || acc.is_selfdestructed() { - accounts.remove(&add); - storage.remove(&add); - } else { - // insert account - if let Some(code_hash) = acc - .info - .code - .as_ref() - .filter(|code| !code.is_empty()) - .map(|code| code.hash_slow()) - { - acc.info.code_hash = code_hash; - } else if acc.info.code_hash.is_zero() { - acc.info.code_hash = KECCAK_EMPTY; - } - accounts.insert(add, acc.info); - - let acc_storage = storage.entry(add).or_default(); - if acc.status.contains(AccountStatus::Created) { - acc_storage.clear(); - } - for (index, value) in acc.storage { - if value.present_value().is_zero() { - acc_storage.remove(&index); - } else { - acc_storage.insert(index, value.present_value()); - } - } - if acc_storage.is_empty() { - storage.remove(&add); - } - } - } - } -} - -impl Clone for MemDb { - fn clone(&self) -> Self { - Self { - storage: RwLock::new(self.storage.read().clone()), - accounts: RwLock::new(self.accounts.read().clone()), - block_hashes: RwLock::new(self.block_hashes.read().clone()), - } - } -} - -impl DatabaseCommit for MemDb { - fn commit(&mut self, changes: Map) { - self.do_commit(changes) - } -} - -/// A DB that stores the cached content in a json file -#[derive(Debug)] -pub struct JsonBlockCacheDB { - /// Where this cache file is stored. - /// - /// If this is a [None] then caching is disabled - cache_path: Option, - /// Object that's stored in a json file - data: JsonBlockCacheData, -} - -impl JsonBlockCacheDB { - /// Creates a new instance. - fn new(meta: Arc>, cache_path: Option) -> Self { - Self { cache_path, data: JsonBlockCacheData { meta, data: Arc::new(Default::default()) } } - } - - /// Loads the contents of the diskmap file and returns the read object - /// - /// # Errors - /// This will fail if - /// - the `path` does not exist - /// - the format does not match [JsonBlockCacheData] - pub fn load(path: impl Into) -> eyre::Result { - let path = path.into(); - trace!(target: "cache", ?path, "reading json cache"); - let contents = std::fs::read_to_string(&path).map_err(|err| { - warn!(?err, ?path, "Failed to read cache file"); - err - })?; - let data = serde_json::from_str(&contents).map_err(|err| { - warn!(target: "cache", ?err, ?path, "Failed to deserialize cache data"); - err - })?; - Ok(Self { cache_path: Some(path), data }) - } - - /// Returns the [MemDb] it holds access to - pub fn db(&self) -> &Arc { - &self.data.data - } - - /// Metadata stored alongside the data - pub fn meta(&self) -> &Arc> { - &self.data.meta - } - - /// Returns `true` if this is a transient cache and nothing will be flushed - pub fn is_transient(&self) -> bool { - self.cache_path.is_none() - } - - /// Flushes the DB to disk if caching is enabled. - #[instrument(level = "warn", skip_all, fields(path = ?self.cache_path))] - pub fn flush(&self) { - let Some(path) = &self.cache_path else { return }; - trace!(target: "cache", "saving json cache"); - - if let Some(parent) = path.parent() { - let _ = fs::create_dir_all(parent); - } - - let file = match fs::File::create(path) { - Ok(file) => file, - Err(e) => return warn!(target: "cache", %e, "Failed to open json cache for writing"), - }; - - let mut writer = BufWriter::new(file); - if let Err(e) = serde_json::to_writer(&mut writer, &self.data) { - return warn!(target: "cache", %e, "Failed to write to json cache") - } - if let Err(e) = writer.flush() { - return warn!(target: "cache", %e, "Failed to flush to json cache") - } - - trace!(target: "cache", "saved json cache"); - } -} - -/// The Data the [JsonBlockCacheDB] can read and flush -/// -/// This will be deserialized in a JSON object with the keys: -/// `["meta", "accounts", "storage", "block_hashes"]` -#[derive(Debug)] -pub struct JsonBlockCacheData { - pub meta: Arc>, - pub data: Arc, -} - -impl Serialize for JsonBlockCacheData { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(4))?; - - map.serialize_entry("meta", &*self.meta.read())?; - map.serialize_entry("accounts", &*self.data.accounts.read())?; - map.serialize_entry("storage", &*self.data.storage.read())?; - map.serialize_entry("block_hashes", &*self.data.block_hashes.read())?; - - map.end() - } -} - -impl<'de> Deserialize<'de> for JsonBlockCacheData { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct Data { - meta: BlockchainDbMeta, - #[serde(flatten)] - data: StateSnapshot, - } - - let Data { meta, data: StateSnapshot { accounts, storage, block_hashes } } = - Data::deserialize(deserializer)?; - - Ok(Self { - meta: Arc::new(RwLock::new(meta)), - data: Arc::new(MemDb { - accounts: RwLock::new(accounts), - storage: RwLock::new(storage), - block_hashes: RwLock::new(block_hashes), - }), - }) - } -} - -/// A type that flushes a `JsonBlockCacheDB` on drop -/// -/// This type intentionally does not implement `Clone` since it's intended that there's only once -/// instance that will flush the cache. -#[derive(Debug)] -pub struct FlushJsonBlockCacheDB(pub Arc); - -impl Drop for FlushJsonBlockCacheDB { - fn drop(&mut self) { - trace!(target: "fork::cache", "flushing cache"); - self.0.flush(); - trace!(target: "fork::cache", "flushed cache"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_deserialize_cache() { - let s = r#"{ - "meta": { - "cfg_env": { - "chain_id": 1337, - "perf_analyse_created_bytecodes": "Analyse", - "limit_contract_code_size": 18446744073709551615, - "memory_limit": 4294967295, - "disable_block_gas_limit": false, - "disable_eip3607": false, - "disable_base_fee": false - }, - "block_env": { - "number": "0xed3ddf", - "coinbase": "0x0000000000000000000000000000000000000000", - "timestamp": "0x6324bc3f", - "difficulty": "0x0", - "basefee": "0x2e5fda223", - "gas_limit": "0x1c9c380", - "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - "hosts": [ - "eth-mainnet.alchemyapi.io" - ] - }, - "accounts": { - "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc": { - "balance": "0x0", - "nonce": 10, - "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 1, - "data": [0] - } - } - } - } - }, - "storage": { - "0xa354f35829ae975e850e23e9615b11da1b3dc4de": { - "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "0x5553444320795661756c74000000000000000000000000000000000000000000", - "0x10": "0x37fd60ff8346", - "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "0xb", - "0x6": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "0x5": "0x36ff5b93162e", - "0x14": "0x29d635a8e000", - "0x11": "0x63224c73", - "0x2": "0x6" - } - }, - "block_hashes": { - "0xed3deb": "0xbf7be3174b261ea3c377b6aba4a1e05d5fae7eee7aab5691087c20cf353e9877", - "0xed3de9": "0xba1c3648e0aee193e7d00dffe4e9a5e420016b4880455641085a4731c1d32eef", - "0xed3de8": "0x61d1491c03a9295fb13395cca18b17b4fa5c64c6b8e56ee9cc0a70c3f6cf9855", - "0xed3de7": "0xb54560b5baeccd18350d56a3bee4035432294dc9d2b7e02f157813e1dee3a0be", - "0xed3dea": "0x816f124480b9661e1631c6ec9ee39350bda79f0cbfc911f925838d88e3d02e4b" - } -}"#; - - let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap(); - assert_eq!(cache.data.accounts.read().len(), 1); - assert_eq!(cache.data.storage.read().len(), 1); - assert_eq!(cache.data.block_hashes.read().len(), 5); - - let _s = serde_json::to_string(&cache).unwrap(); - } - - #[test] - fn can_deserialize_cache_post_4844() { - let s = r#"{ - "meta": { - "cfg_env": { - "chain_id": 1, - "kzg_settings": "Default", - "perf_analyse_created_bytecodes": "Analyse", - "limit_contract_code_size": 18446744073709551615, - "memory_limit": 134217728, - "disable_block_gas_limit": false, - "disable_eip3607": true, - "disable_base_fee": false, - "optimism": false - }, - "block_env": { - "number": "0x11c99bc", - "coinbase": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", - "timestamp": "0x65627003", - "gas_limit": "0x1c9c380", - "basefee": "0x64288ff1f", - "difficulty": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", - "prevrandao": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", - "blob_excess_gas_and_price": { - "excess_blob_gas": 0, - "blob_gasprice": 1 - } - }, - "hosts": [ - "eth-mainnet.alchemyapi.io" - ] - }, - "accounts": { - "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { - "balance": "0x8e0c373cfcdfd0eb", - "nonce": 128912, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 1, - "data": [0] - } - } - } - } - }, - "storage": {}, - "block_hashes": {} -}"#; - - let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap(); - assert_eq!(cache.data.accounts.read().len(), 1); - - let _s = serde_json::to_string(&cache).unwrap(); - } -} diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 2712b5779..de6b2a6b9 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -1,12 +1,12 @@ //! A revm database that forks off a remote client use crate::{ - backend::{DatabaseError, RevertSnapshotAction, StateSnapshot}, - fork::{BlockchainDb, SharedBackend}, + backend::{RevertSnapshotAction, StateSnapshot}, snapshot::Snapshots, }; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; +use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; use parking_lot::Mutex; use revm::{ db::{CacheDB, DatabaseRef}, @@ -167,7 +167,7 @@ impl Database for ForkedDatabase { Database::storage(&mut self.cache_db, address, index) } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { Database::block_hash(&mut self.cache_db, number) } } @@ -187,7 +187,7 @@ impl DatabaseRef for ForkedDatabase { DatabaseRef::storage_ref(&self.cache_db, address, index) } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { self.cache_db.block_hash_ref(number) } } @@ -253,8 +253,8 @@ impl DatabaseRef for ForkDbSnapshot { } } - fn block_hash_ref(&self, number: U256) -> Result { - match self.snapshot.block_hashes.get(&number).copied() { + fn block_hash_ref(&self, number: u64) -> Result { + match self.snapshot.block_hashes.get(&U256::from(number)).copied() { None => self.local.block_hash_ref(number), Some(block_hash) => Ok(block_hash), } @@ -264,7 +264,7 @@ impl DatabaseRef for ForkDbSnapshot { #[cfg(test)] mod tests { use super::*; - use crate::fork::BlockchainDbMeta; + use crate::backend::BlockchainDbMeta; use foundry_common::provider::get_http_provider; use std::collections::BTreeSet; diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index b69e02aad..f60b99cb9 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -10,7 +10,6 @@ use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; /// Initializes a REVM block environment based on a forked /// ethereum provider. -// todo(onbjerg): these bounds needed cus of the bounds in `Provider`, can simplify? pub async fn environment>( provider: &P, memory_limit: u64, diff --git a/crates/evm/core/src/fork/mod.rs b/crates/evm/core/src/fork/mod.rs index a6387b0bb..9401c2d32 100644 --- a/crates/evm/core/src/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -1,18 +1,9 @@ use super::opts::EvmOpts; use revm::primitives::Env; -mod backend; -pub use backend::{BackendHandler, SharedBackend}; - mod init; pub use init::environment; -mod cache; -pub use cache::{ - BlockchainDb, BlockchainDbMeta, FlushJsonBlockCacheDB, JsonBlockCacheDB, JsonBlockCacheData, - MemDb, StorageInfo, -}; - pub mod database; mod multi; diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 2e04b5d90..e813dcada 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -1,15 +1,17 @@ -//! Support for running multiple fork backends +//! Support for running multiple fork backends. //! //! The design is similar to the single `SharedBackend`, `BackendHandler` but supports multiple //! concurrently active pairs at once. -use crate::fork::{BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, SharedBackend}; -use alloy_provider::{Provider, RootProvider}; -use alloy_transport::TransportResult; +use super::CreateFork; +use alloy_primitives::U256; +use alloy_provider::RootProvider; +use alloy_transport::layers::RetryBackoffService; use foundry_common::provider::{ - runtime_transport::RuntimeTransport, tower::RetryBackoffService, ProviderBuilder, RetryProvider, + runtime_transport::RuntimeTransport, ProviderBuilder, RetryProvider, }; use foundry_config::Config; +use foundry_fork_db::{cache::BlockchainDbMeta, BackendHandler, BlockchainDb, SharedBackend}; use futures::{ channel::mpsc::{channel, Receiver, Sender}, stream::{Fuse, Stream}, @@ -65,30 +67,24 @@ impl> From for ForkId { } /// The Sender half of multi fork pair. -/// Can send requests to the `MultiForkHandler` to create forks +/// Can send requests to the `MultiForkHandler` to create forks. #[derive(Clone, Debug)] +#[must_use] pub struct MultiFork { - /// Channel to send `Request`s to the handler + /// Channel to send `Request`s to the handler. handler: Sender, - /// Ensures that all rpc resources get flushed properly + /// Ensures that all rpc resources get flushed properly. _shutdown: Arc, } impl MultiFork { - /// Creates a new pair multi fork pair - pub fn new() -> (Self, MultiForkHandler) { - let (handler, handler_rx) = channel(1); - let _shutdown = Arc::new(ShutDownMultiFork { handler: Some(handler.clone()) }); - (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx)) - } - /// Creates a new pair and spawns the `MultiForkHandler` on a background thread. pub fn spawn() -> Self { trace!(target: "fork::multi", "spawning multifork"); let (fork, mut handler) = Self::new(); - // spawn a light-weight thread with a thread-local async runtime just for - // sending and receiving data from the remote client(s) + // Spawn a light-weight thread with a thread-local async runtime just for + // sending and receiving data from the remote client(s). std::thread::Builder::new() .name("multi-fork-backend".into()) .spawn(move || { @@ -98,10 +94,10 @@ impl MultiFork { .expect("failed to build tokio runtime"); rt.block_on(async move { - // flush cache every 60s, this ensures that long-running fork tests get their - // cache flushed from time to time + // Flush cache every 60s, this ensures that long-running fork tests get their + // cache flushed from time to time. // NOTE: we install the interval here because the `tokio::timer::Interval` - // requires a rt + // requires a rt. handler.set_flush_cache_interval(Duration::from_secs(60)); handler.await }); @@ -111,9 +107,19 @@ impl MultiFork { fork } - /// Returns a fork backend + /// Creates a new pair multi fork pair. + /// + /// Use [`spawn`](Self::spawn) instead. + #[doc(hidden)] + pub fn new() -> (Self, MultiForkHandler) { + let (handler, handler_rx) = channel(1); + let _shutdown = Arc::new(ShutDownMultiFork { handler: Some(handler.clone()) }); + (Self { handler, _shutdown }, MultiForkHandler::new(handler_rx)) + } + + /// Returns a fork backend. /// - /// If no matching fork backend exists it will be created + /// If no matching fork backend exists it will be created. pub fn create_fork(&self, fork: CreateFork) -> eyre::Result<(ForkId, SharedBackend, Env)> { trace!("Creating new fork, url={}, block={:?}", fork.url, fork.evm_opts.fork_block_number); let (sender, rx) = oneshot_channel(); @@ -122,9 +128,9 @@ impl MultiFork { rx.recv()? } - /// Rolls the block of the fork + /// Rolls the block of the fork. /// - /// If no matching fork backend exists it will be created + /// If no matching fork backend exists it will be created. pub fn roll_fork( &self, fork: ForkId, @@ -137,7 +143,7 @@ impl MultiFork { rx.recv()? } - /// Returns the `Env` of the given fork, if any + /// Returns the `Env` of the given fork, if any. pub fn get_env(&self, fork: ForkId) -> eyre::Result> { trace!(?fork, "getting env config"); let (sender, rx) = oneshot_channel(); @@ -146,7 +152,16 @@ impl MultiFork { Ok(rx.recv()?) } - /// Returns the corresponding fork if it exists + /// Updates block number and timestamp of given fork with new values. + pub fn update_block(&self, fork: ForkId, number: U256, timestamp: U256) -> eyre::Result<()> { + trace!(?fork, ?number, ?timestamp, "update fork block"); + self.handler + .clone() + .try_send(Request::UpdateBlock(fork, number, timestamp)) + .map_err(|e| eyre::eyre!("{:?}", e)) + } + + /// Returns the corresponding fork if it exists. /// /// Returns `None` if no matching fork backend is available. pub fn get_fork(&self, id: impl Into) -> eyre::Result> { @@ -158,7 +173,7 @@ impl MultiFork { Ok(rx.recv()?) } - /// Returns the corresponding fork url if it exists + /// Returns the corresponding fork url if it exists. /// /// Returns `None` if no matching fork is available. pub fn get_fork_url(&self, id: impl Into) -> eyre::Result> { @@ -176,37 +191,39 @@ type CreateFuture = type CreateSender = OneshotSender>; type GetEnvSender = OneshotSender>; -/// Request that's send to the handler +/// Request that's send to the handler. #[derive(Debug)] enum Request { - /// Creates a new ForkBackend + /// Creates a new ForkBackend. CreateFork(Box, CreateSender), - /// Returns the Fork backend for the `ForkId` if it exists + /// Returns the Fork backend for the `ForkId` if it exists. GetFork(ForkId, OneshotSender>), - /// Adjusts the block that's being forked, by creating a new fork at the new block + /// Adjusts the block that's being forked, by creating a new fork at the new block. RollFork(ForkId, u64, CreateSender), - /// Returns the environment of the fork + /// Returns the environment of the fork. GetEnv(ForkId, GetEnvSender), + /// Updates the block number and timestamp of the fork. + UpdateBlock(ForkId, U256, U256), /// Shutdowns the entire `MultiForkHandler`, see `ShutDownMultiFork` ShutDown(OneshotSender<()>), - /// Returns the Fork Url for the `ForkId` if it exists + /// Returns the Fork Url for the `ForkId` if it exists. GetForkUrl(ForkId, OneshotSender>), } enum ForkTask { - /// Contains the future that will establish a new fork + /// Contains the future that will establish a new fork. Create(CreateFuture, ForkId, CreateSender, Vec), } -/// The type that manages connections in the background +/// The type that manages connections in the background. #[must_use = "futures do nothing unless polled"] pub struct MultiForkHandler { /// Incoming requests from the `MultiFork`. incoming: Fuse>, - /// All active handlers + /// All active handlers. /// - /// It's expected that this list will be rather small (<10) + /// It's expected that this list will be rather small (<10). handlers: Vec<(ForkId, Handler)>, // tasks currently in progress @@ -218,7 +235,7 @@ pub struct MultiForkHandler { /// block number. forks: HashMap, - /// Optional periodic interval to flush rpc cache + /// Optional periodic interval to flush rpc cache. flush_cache_interval: Option, } @@ -233,7 +250,7 @@ impl MultiForkHandler { } } - /// Sets the interval after which all rpc caches should be flushed periodically + /// Sets the interval after which all rpc caches should be flushed periodically. pub fn set_flush_cache_interval(&mut self, period: Duration) -> &mut Self { self.flush_cache_interval = Some(tokio::time::interval_at(tokio::time::Instant::now() + period, period)); @@ -257,13 +274,13 @@ impl MultiForkHandler { let fork_id = ForkId::new(&fork.url, fork.evm_opts.fork_block_number); trace!(?fork_id, "created new forkId"); - // there could already be a task for the requested fork in progress + // There could already be a task for the requested fork in progress. if let Some(in_progress) = self.find_in_progress_task(&fork_id) { in_progress.push(sender); return; } - // need to create a new fork + // Need to create a new fork. let task = Box::pin(create_fork(fork)); self.pending_tasks.push(ForkTask::Create(task, fork_id, sender, Vec::new())); } @@ -278,7 +295,7 @@ impl MultiForkHandler { self.forks.insert(fork_id.clone(), fork.clone()); let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.opts.env.clone()))); - // notify all additional senders and track unique forkIds + // Notify all additional senders and track unique forkIds. for sender in additional_senders { let next_fork_id = fork.inc_senders(fork_id.clone()); self.forks.insert(next_fork_id.clone(), fork.clone()); @@ -286,6 +303,15 @@ impl MultiForkHandler { } } + /// Update fork block number and timestamp. Used to preserve values set by `roll` and `warp` + /// cheatcodes when new fork selected. + fn update_block(&mut self, fork_id: ForkId, block_number: U256, block_timestamp: U256) { + if let Some(fork) = self.forks.get_mut(&fork_id) { + fork.opts.env.block.number = block_number; + fork.opts.env.block.timestamp = block_timestamp; + } + } + fn on_request(&mut self, req: Request) { match req { Request::CreateFork(fork, sender) => self.create_fork(*fork, sender), @@ -306,9 +332,12 @@ impl MultiForkHandler { Request::GetEnv(fork_id, sender) => { let _ = sender.send(self.forks.get(&fork_id).map(|fork| fork.opts.env.clone())); } + Request::UpdateBlock(fork_id, block_number, block_timestamp) => { + self.update_block(fork_id, block_number, block_timestamp); + } Request::ShutDown(sender) => { trace!(target: "fork::multi", "received shutdown signal"); - // we're emptying all fork backends, this way we ensure all caches get flushed + // We're emptying all fork backends, this way we ensure all caches get flushed. self.forks.clear(); self.handlers.clear(); let _ = sender.send(()); @@ -321,22 +350,22 @@ impl MultiForkHandler { } } -// Drives all handler to completion -// This future will finish once all underlying BackendHandler are completed +// Drives all handler to completion. +// This future will finish once all underlying BackendHandler are completed. impl Future for MultiForkHandler { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); - // receive new requests + // Receive new requests. loop { match Pin::new(&mut pin.incoming).poll_next(cx) { Poll::Ready(Some(req)) => { pin.on_request(req); } Poll::Ready(None) => { - // channel closed, but we still need to drive the fork handlers to completion + // Channel closed, but we still need to drive the fork handlers to completion. trace!(target: "fork::multi", "request channel closed"); break; } @@ -344,7 +373,7 @@ impl Future for MultiForkHandler { } } - // advance all tasks + // Advance all tasks. for n in (0..pin.pending_tasks.len()).rev() { let task = pin.pending_tasks.swap_remove(n); match task { @@ -383,7 +412,7 @@ impl Future for MultiForkHandler { } } - // advance all handlers + // Advance all handlers. for n in (0..pin.handlers.len()).rev() { let (id, mut handler) = pin.handlers.swap_remove(n); match handler.poll_unpin(cx) { @@ -401,7 +430,7 @@ impl Future for MultiForkHandler { return Poll::Ready(()); } - // periodically flush cached RPC state + // Periodically flush cached RPC state. if pin .flush_cache_interval .as_mut() @@ -411,7 +440,7 @@ impl Future for MultiForkHandler { { trace!(target: "fork::multi", "tick flushing caches"); let forks = pin.forks.values().map(|f| f.backend.clone()).collect::>(); - // flush this on new thread to not block here + // Flush this on new thread to not block here. std::thread::Builder::new() .name("flusher".into()) .spawn(move || { @@ -427,12 +456,12 @@ impl Future for MultiForkHandler { /// Tracks the created Fork #[derive(Debug, Clone)] struct CreatedFork { - /// How the fork was initially created + /// How the fork was initially created. opts: CreateFork, - /// Copy of the sender + /// Copy of the sender. backend: SharedBackend, /// How many consumers there are, since a `SharedBacked` can be used by multiple - /// consumers + /// consumers. num_senders: Arc, } @@ -441,7 +470,7 @@ impl CreatedFork { Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } } - /// Increment senders and return unique identifier of the fork + /// Increment senders and return unique identifier of the fork. fn inc_senders(&self, fork_id: ForkId) -> ForkId { format!( "{}-{}", @@ -454,7 +483,7 @@ impl CreatedFork { /// A type that's used to signaling the `MultiForkHandler` when it's time to shut down. /// -/// This is essentially a sync on drop, so that the `MultiForkHandler` can flush all rpc cashes +/// This is essentially a sync on drop, so that the `MultiForkHandler` can flush all rpc cashes. /// /// This type intentionally does not implement `Clone` since it's intended that there's only once /// instance. @@ -477,9 +506,9 @@ impl Drop for ShutDownMultiFork { } } -/// Creates a new fork +/// Creates a new fork. /// -/// This will establish a new `Provider` to the endpoint and return the Fork Backend +/// This will establish a new `Provider` to the endpoint and return the Fork Backend. async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { let provider: Arc< RootProvider, alloy_provider::network::AnyNetwork>, @@ -491,16 +520,16 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, .build()?, ); - // initialise the fork environment + // Initialise the fork environment. let (env, block) = fork.evm_opts.fork_evm_env(&fork.url).await?; fork.env = env; let meta = BlockchainDbMeta::new(fork.env.clone(), fork.url.clone()); - // we need to use the block number from the block because the env's number can be different on + // We need to use the block number from the block because the env's number can be different on // some L2s (e.g. Arbitrum). let number = block.header.number.unwrap_or(meta.block_env.number.to()); - // determine the cache path if caching is enabled + // Determine the cache path if caching is enabled. let cache_path = if fork.enable_caching { Config::foundry_block_cache_dir(meta.cfg_env.chain_id, number) } else { @@ -514,27 +543,3 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Ok((fork_id, fork, handler)) } - -impl super::backend::ZkSyncMiddleware - for RootProvider -{ - async fn get_bytecode_by_hash( - &self, - hash: alloy_primitives::B256, - ) -> TransportResult> { - let bytecode: Option = - self.raw_request("zks_getBytecodeByHash".into(), vec![hash]).await?; - Ok(bytecode.map(revm::primitives::Bytecode::new_raw)) - } -} - -impl super::backend::ZkSyncMiddleware - for Arc> -{ - async fn get_bytecode_by_hash( - &self, - hash: alloy_primitives::B256, - ) -> TransportResult> { - self.as_ref().get_bytecode_by_hash(hash).await - } -} diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index c2792ab87..acb9cc50e 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -4,6 +4,7 @@ use rustc_hash::FxHashMap; /// Maps from program counter to instruction counter. /// /// Inverse of [`IcPcMap`]. +#[derive(Debug, Clone)] pub struct PcIcMap { pub inner: FxHashMap, } diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index c2c2e2006..ed10c5d75 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -12,16 +12,20 @@ use revm_inspectors::access_list::AccessListInspector; #[macro_use] extern crate tracing; +pub mod abi { + pub use foundry_cheatcodes_spec::Vm; + pub use foundry_evm_abi::*; +} + mod ic; -pub mod abi; pub mod backend; pub mod constants; -pub mod debug; pub mod decode; pub mod fork; pub mod opcodes; pub mod opts; +pub mod precompiles; pub mod snapshot; pub mod utils; @@ -40,6 +44,9 @@ pub trait InspectorExt: Inspector { ) -> bool { false } + + // Simulates `console.log` invocation. + fn console_log(&mut self, _input: String) {} } impl InspectorExt for NoOpInspector {} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 4ff429902..d676c90b3 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -123,13 +123,13 @@ impl EvmOpts { difficulty: U256::from(self.env.block_difficulty), prevrandao: Some(self.env.block_prevrandao), basefee: U256::from(self.env.block_base_fee_per_gas), - gas_limit: self.gas_limit(), + gas_limit: U256::from(self.gas_limit()), ..Default::default() }, cfg, tx: TxEnv { gas_price: U256::from(self.env.gas_price.unwrap_or_default()), - gas_limit: self.gas_limit().to(), + gas_limit: self.gas_limit(), caller: self.sender, ..Default::default() }, @@ -156,8 +156,8 @@ impl EvmOpts { } /// Returns the gas limit to use - pub fn gas_limit(&self) -> U256 { - U256::from(self.env.block_gas_limit.unwrap_or(self.env.gas_limit)) + pub fn gas_limit(&self) -> u64 { + self.env.block_gas_limit.unwrap_or(self.env.gas_limit) } /// Returns the configured chain id, which will be diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs new file mode 100644 index 000000000..03ab18dff --- /dev/null +++ b/crates/evm/core/src/precompiles.rs @@ -0,0 +1,45 @@ +use alloy_primitives::{address, Address}; + +/// The ECRecover precompile address. +pub const EC_RECOVER: Address = address!("0000000000000000000000000000000000000001"); + +/// The SHA-256 precompile address. +pub const SHA_256: Address = address!("0000000000000000000000000000000000000002"); + +/// The RIPEMD-160 precompile address. +pub const RIPEMD_160: Address = address!("0000000000000000000000000000000000000003"); + +/// The Identity precompile address. +pub const IDENTITY: Address = address!("0000000000000000000000000000000000000004"); + +/// The ModExp precompile address. +pub const MOD_EXP: Address = address!("0000000000000000000000000000000000000005"); + +/// The ECAdd precompile address. +pub const EC_ADD: Address = address!("0000000000000000000000000000000000000006"); + +/// The ECMul precompile address. +pub const EC_MUL: Address = address!("0000000000000000000000000000000000000007"); + +/// The ECPairing precompile address. +pub const EC_PAIRING: Address = address!("0000000000000000000000000000000000000008"); + +/// The Blake2F precompile address. +pub const BLAKE_2F: Address = address!("0000000000000000000000000000000000000009"); + +/// The PointEvaluation precompile address. +pub const POINT_EVALUATION: Address = address!("000000000000000000000000000000000000000a"); + +/// Precompile addresses. +pub const PRECOMPILES: &[Address] = &[ + EC_RECOVER, + SHA_256, + RIPEMD_160, + IDENTITY, + MOD_EXP, + EC_ADD, + EC_MUL, + EC_PAIRING, + BLAKE_2F, + POINT_EVALUATION, +]; diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index d8114f209..76a738c52 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,7 +1,7 @@ pub use crate::ic::*; use crate::{constants::DEFAULT_CREATE2_DEPLOYER, InspectorExt}; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Selector, U256}; +use alloy_primitives::{Address, Selector, TxKind, U256}; use alloy_rpc_types::{Block, Transaction}; use foundry_config::NamedChain; use revm::{ @@ -11,7 +11,7 @@ use revm::{ return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, Gas, InstructionResult, InterpreterResult, }, - primitives::{CreateScheme, EVMError, SpecId, TransactTo, KECCAK_EMPTY}, + primitives::{CreateScheme, EVMError, HandlerCfg, SpecId, KECCAK_EMPTY}, FrameOrResult, FrameResult, }; use std::{cell::RefCell, rc::Rc, sync::Arc}; @@ -77,25 +77,10 @@ pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { env.tx.gas_price = U256::from(tx.gas_price.unwrap_or_default()); env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(U256::from); env.tx.nonce = Some(tx.nonce); - env.tx.access_list = tx - .access_list - .clone() - .unwrap_or_default() - .0 - .into_iter() - .map(|item| { - ( - item.address, - item.storage_keys - .into_iter() - .map(|key| alloy_primitives::U256::from_be_bytes(key.0)) - .collect(), - ) - }) - .collect(); + env.tx.access_list = tx.access_list.clone().unwrap_or_default().0.into_iter().collect(); env.tx.value = tx.value.to(); env.tx.data = alloy_primitives::Bytes(tx.input.0.clone()); - env.tx.transact_to = tx.to.map(TransactTo::Call).unwrap_or_else(TransactTo::create) + env.tx.transact_to = tx.to.map(TxKind::Call).unwrap_or(TxKind::Create) } /// Get the gas used, accounting for refunds @@ -156,11 +141,6 @@ pub fn create2_handler_register>( .borrow_mut() .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); - // Handle potential inspector override. - if let Some(outcome) = outcome { - return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); - } - // Sanity check that CREATE2 deployer exists. let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.0.info.code_hash; if code_hash == KECCAK_EMPTY { @@ -174,6 +154,11 @@ pub fn create2_handler_register>( }))) } + // Handle potential inspector override. + if let Some(outcome) = outcome { + return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); + } + // Create CALL frame for CREATE2 factory invocation. let mut frame_or_result = ctx.evm.make_call_frame(&call_inputs); @@ -184,9 +169,8 @@ pub fn create2_handler_register>( frame_or_result }); - let create2_overrides_inner = create2_overrides.clone(); + let create2_overrides_inner = create2_overrides; let old_handle = handler.execution.insert_call_outcome.clone(); - handler.execution.insert_call_outcome = Arc::new(move |ctx, frame, shared_memory, mut outcome| { // If we are on the depth of the latest override, handle the outcome. @@ -268,6 +252,23 @@ where new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) } +pub fn new_evm_with_existing_context<'a, DB, I>( + inner: revm::InnerEvmContext, + inspector: I, +) -> revm::Evm<'a, I, DB> +where + DB: revm::Database, + I: InspectorExt, +{ + let handler_cfg = HandlerCfg::new(inner.spec_id()); + let context = + revm::Context::new(revm::EvmContext { inner, precompiles: Default::default() }, inspector); + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + handler.append_handler_register_plain(create2_handler_register); + revm::Evm::new(context, handler) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index bbfaa1897..b110f6e69 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -38,7 +38,7 @@ impl<'a> ContractVisitor<'a> { self.visit_function_definition(node)?; } NodeType::ModifierDefinition => { - self.visit_modifier_definition(node)?; + self.visit_modifier_or_yul_fn_definition(node)?; } _ => {} } @@ -70,7 +70,7 @@ impl<'a> ContractVisitor<'a> { } } - fn visit_modifier_definition(&mut self, node: &Node) -> eyre::Result<()> { + fn visit_modifier_or_yul_fn_definition(&mut self, node: &Node) -> eyre::Result<()> { let name: String = node.attribute("name").ok_or_else(|| eyre::eyre!("Modifier has no name"))?; @@ -98,7 +98,6 @@ impl<'a> ContractVisitor<'a> { } fn visit_statement(&mut self, node: &Node) -> eyre::Result<()> { - // TODO: YulSwitch, YulForLoop, YulFunctionDefinition, YulVariableDeclaration match node.node_type { // Blocks NodeType::Block | NodeType::UncheckedBlock | NodeType::YulBlock => { @@ -118,7 +117,8 @@ impl<'a> ContractVisitor<'a> { NodeType::YulAssignment | NodeType::YulBreak | NodeType::YulContinue | - NodeType::YulLeave => { + NodeType::YulLeave | + NodeType::YulVariableDeclaration => { self.push_item(CoverageItem { kind: CoverageItemKind::Statement, loc: self.source_location_for(&node.src), @@ -126,10 +126,8 @@ impl<'a> ContractVisitor<'a> { }); Ok(()) } - // Skip placeholder statements as they are never referenced in source maps. NodeType::PlaceholderStatement => Ok(()), - // Return with eventual subcall NodeType::Return => { self.push_item(CoverageItem { @@ -142,7 +140,6 @@ impl<'a> ContractVisitor<'a> { } Ok(()) } - // Variable declaration NodeType::VariableDeclarationStatement => { self.push_item(CoverageItem { @@ -199,7 +196,7 @@ impl<'a> ContractVisitor<'a> { self.visit_expression( &node .attribute("condition") - .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, + .ok_or_else(|| eyre::eyre!("if statement had no condition"))?, )?; let true_body: Node = node @@ -211,32 +208,63 @@ impl<'a> ContractVisitor<'a> { let branch_id = self.branch_id; // We increase the branch ID here such that nested branches do not use the same - // branch ID as we do + // branch ID as we do. self.branch_id += 1; - // The relevant source range for the branch is the `if(...)` statement itself and - // the true body of the if statement. The false body of the statement (if any) is - // processed as its own thing. If this source range is not processed like this, it - // is virtually impossible to correctly map instructions back to branches that - // include more complex logic like conditional logic. - self.push_branches( - &foundry_compilers::artifacts::ast::LowFidelitySourceLocation { - start: node.src.start, - length: true_body - .src - .length - .map(|length| true_body.src.start - node.src.start + length), - index: node.src.index, - }, - branch_id, - ); - - // Process the true branch - self.visit_block_or_statement(&true_body)?; - - // Process the false branch - if let Some(false_body) = node.attribute("falseBody") { - self.visit_block_or_statement(&false_body)?; + // The relevant source range for the true branch is the `if(...)` statement itself + // and the true body of the if statement. The false body of the + // statement (if any) is processed as its own thing. If this source + // range is not processed like this, it is virtually impossible to + // correctly map instructions back to branches that include more + // complex logic like conditional logic. + let true_branch_loc = &ast::LowFidelitySourceLocation { + start: node.src.start, + length: true_body + .src + .length + .map(|length| true_body.src.start - node.src.start + length), + index: node.src.index, + }; + + // Add the coverage item for branch 0 (true body). + self.push_item(CoverageItem { + kind: CoverageItemKind::Branch { branch_id, path_id: 0 }, + loc: self.source_location_for(true_branch_loc), + hits: 0, + }); + + match node.attribute::("falseBody") { + // Both if/else statements. + Some(false_body) => { + // Add the coverage item for branch 1 (false body). + // The relevant source range for the false branch is the `else` statement + // itself and the false body of the else statement. + self.push_item(CoverageItem { + kind: CoverageItemKind::Branch { branch_id, path_id: 1 }, + loc: self.source_location_for(&ast::LowFidelitySourceLocation { + start: node.src.start, + length: false_body.src.length.map(|length| { + false_body.src.start - true_body.src.start + length + }), + index: node.src.index, + }), + hits: 0, + }); + // Process the true body. + self.visit_block_or_statement(&true_body)?; + // Process the false body. + self.visit_block_or_statement(&false_body)?; + } + None => { + // Add the coverage item for branch 1 (same true body). + self.push_item(CoverageItem { + kind: CoverageItemKind::Branch { branch_id, path_id: 1 }, + loc: self.source_location_for(true_branch_loc), + hits: 0, + }); + // Process the true body. + self.visit_block_or_statement(&true_body)?; + } } Ok(()) @@ -269,16 +297,91 @@ impl<'a> ContractVisitor<'a> { Ok(()) } - // Try-catch statement + // Try-catch statement. Coverage is reported for expression, for each clause and their + // bodies (if any). NodeType::TryStatement => { - // TODO: Clauses - // TODO: This is branching, right? self.visit_expression( &node .attribute("externalCall") .ok_or_else(|| eyre::eyre!("try statement had no call"))?, - ) + )?; + + // Add coverage for each Try-catch clause. + for clause in node + .attribute::>("clauses") + .ok_or_else(|| eyre::eyre!("try statement had no clause"))? + { + // Add coverage for clause statement. + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&clause.src), + hits: 0, + }); + self.visit_statement(&clause)?; + + // Add coverage for clause body only if it is not empty. + if let Some(block) = clause.attribute::("block") { + let statements: Vec = + block.attribute("statements").unwrap_or_default(); + if !statements.is_empty() { + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&block.src), + hits: 0, + }); + self.visit_block(&block)?; + } + } + } + + Ok(()) + } + NodeType::YulSwitch => { + // Add coverage for each case statement amd their bodies. + for case in node + .attribute::>("cases") + .ok_or_else(|| eyre::eyre!("yul switch had no case"))? + { + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&case.src), + hits: 0, + }); + self.visit_statement(&case)?; + + if let Some(body) = case.body { + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&body.src), + hits: 0, + }); + self.visit_block(&body)? + } + } + Ok(()) } + NodeType::YulForLoop => { + if let Some(condition) = node.attribute("condition") { + self.visit_expression(&condition)?; + } + if let Some(pre) = node.attribute::("pre") { + self.visit_block(&pre)? + } + if let Some(post) = node.attribute::("post") { + self.visit_block(&post)? + } + + if let Some(body) = &node.body { + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&body.src), + hits: 0, + }); + self.visit_block(body)? + } + Ok(()) + } + NodeType::YulFunctionDefinition => self.visit_modifier_or_yul_fn_definition(node), _ => { warn!("unexpected node type, expected a statement: {:?}", node.node_type); Ok(()) @@ -287,14 +390,11 @@ impl<'a> ContractVisitor<'a> { } fn visit_expression(&mut self, node: &Node) -> eyre::Result<()> { - // TODO - // elementarytypenameexpression - // memberaccess - // newexpression - // tupleexpression - // yulfunctioncall match node.node_type { - NodeType::Assignment | NodeType::UnaryOperation => { + NodeType::Assignment | + NodeType::UnaryOperation | + NodeType::Conditional | + NodeType::YulFunctionCall => { self.push_item(CoverageItem { kind: CoverageItemKind::Statement, loc: self.source_location_for(&node.src), @@ -302,6 +402,40 @@ impl<'a> ContractVisitor<'a> { }); Ok(()) } + NodeType::FunctionCall => { + // Do not count other kinds of calls towards coverage (like `typeConversion` + // and `structConstructorCall`). + let kind: Option = node.attribute("kind"); + if let Some("functionCall") = kind.as_deref() { + self.push_item(CoverageItem { + kind: CoverageItemKind::Statement, + loc: self.source_location_for(&node.src), + hits: 0, + }); + + let expr: Option = node.attribute("expression"); + if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { + // Might be a require call, add branch coverage. + let name: Option = expr.and_then(|expr| expr.attribute("name")); + if let Some("require") = name.as_deref() { + let branch_id = self.branch_id; + self.branch_id += 1; + self.push_item(CoverageItem { + kind: CoverageItemKind::Branch { branch_id, path_id: 0 }, + loc: self.source_location_for(&node.src), + hits: 0, + }); + self.push_item(CoverageItem { + kind: CoverageItemKind::Branch { branch_id, path_id: 1 }, + loc: self.source_location_for(&node.src), + hits: 0, + }); + } + } + } + + Ok(()) + } NodeType::BinaryOperation => { self.push_item(CoverageItem { kind: CoverageItemKind::Statement, @@ -322,33 +456,6 @@ impl<'a> ContractVisitor<'a> { Ok(()) } - NodeType::FunctionCall => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - - let expr: Option = node.attribute("expression"); - if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { - // Might be a require/assert call - let name: Option = expr.and_then(|expr| expr.attribute("name")); - if let Some("assert" | "require") = name.as_deref() { - self.push_branches(&node.src, self.branch_id); - self.branch_id += 1; - } - } - - Ok(()) - } - NodeType::Conditional => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - Ok(()) - } // Does not count towards coverage NodeType::FunctionCallOptions | NodeType::Identifier | @@ -379,6 +486,7 @@ impl<'a> ContractVisitor<'a> { NodeType::RevertStatement | NodeType::TryStatement | NodeType::VariableDeclarationStatement | + NodeType::YulVariableDeclaration | NodeType::WhileStatement => self.visit_statement(node), // Skip placeholder statements as they are never referenced in source maps. NodeType::PlaceholderStatement => Ok(()), @@ -417,19 +525,6 @@ impl<'a> ContractVisitor<'a> { line: self.source[..loc.start].lines().count(), } } - - fn push_branches(&mut self, loc: &ast::LowFidelitySourceLocation, branch_id: usize) { - self.push_item(CoverageItem { - kind: CoverageItemKind::Branch { branch_id, path_id: 0 }, - loc: self.source_location_for(loc), - hits: 0, - }); - self.push_item(CoverageItem { - kind: CoverageItemKind::Branch { branch_id, path_id: 1 }, - loc: self.source_location_for(loc), - hits: 0, - }); - } } /// [`SourceAnalyzer`] result type. @@ -493,7 +588,7 @@ impl<'a> SourceAnalyzer<'a> { let is_test = items.iter().any(|item| { if let CoverageItemKind::Function { name } = &item.kind { - name.is_test() + name.is_any_test() } else { false } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index ad4ad744d..983cc77d4 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -149,7 +149,7 @@ pub fn find_anchor_branch( ItemAnchor { item_id, // The first branch is the opcode directly after JUMPI - instruction: pc + 2, + instruction: pc + 1, }, ItemAnchor { item_id, instruction: pc_jump }, )); diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 22f4b9808..67822bd8e 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -9,18 +9,17 @@ extern crate tracing; use alloy_primitives::{Bytes, B256}; +use eyre::{Context, Result}; use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; use std::{ collections::{BTreeMap, HashMap}, fmt::Display, ops::{AddAssign, Deref, DerefMut}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, }; -use eyre::{Context, Result}; - pub mod analysis; pub mod anchors; @@ -91,8 +90,7 @@ impl CoverageReport { else { continue; }; - let mut summary = summaries.entry(path).or_default(); - summary += item; + *summaries.entry(path).or_default() += item; } } @@ -150,6 +148,22 @@ impl CoverageReport { } Ok(()) } + + /// Removes all the coverage items that should be ignored by the filter. + /// + /// 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| { + self.source_paths + .get(&(version.clone(), item.loc.source_id)) + .map(|path| filter(path)) + .unwrap_or(false) + }); + !items.is_empty() + }); + } } /// A collection of [`HitMap`]s. @@ -409,34 +423,3 @@ impl AddAssign<&CoverageItem> for CoverageSummary { } } } - -impl AddAssign<&CoverageItem> for &mut CoverageSummary { - fn add_assign(&mut self, item: &CoverageItem) { - match item.kind { - CoverageItemKind::Line => { - self.line_count += 1; - if item.hits > 0 { - self.line_hits += 1; - } - } - CoverageItemKind::Statement => { - self.statement_count += 1; - if item.hits > 0 { - self.statement_hits += 1; - } - } - CoverageItemKind::Branch { .. } => { - self.branch_count += 1; - if item.hits > 0 { - self.branch_hits += 1; - } - } - CoverageItemKind::Function { .. } => { - self.function_count += 1; - if item.hits > 0 { - self.function_hits += 1; - } - } - } - } -} diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index ab10c56b5..384a88d6b 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -45,10 +45,9 @@ revm = { workspace = true, default-features = false, features = [ ] } revm-inspectors.workspace = true -arrayvec.workspace = true eyre.workspace = true parking_lot.workspace = true -proptest = "1" +proptest.workspace = true thiserror.workspace = true tracing.workspace = true itertools.workspace = true diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index 356f2332b..3e6b3a1a8 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,5 +1,4 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; -use alloy_primitives::U256; use foundry_evm_core::backend::Backend; use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; @@ -16,11 +15,12 @@ pub struct ExecutorBuilder { /// The configuration used to build an `InspectorStack`. stack: InspectorStackBuilder, /// The gas limit. - gas_limit: Option, + gas_limit: Option, /// The spec ID. spec_id: SpecId, use_zk: bool, + legacy_assertions: bool, } impl Default for ExecutorBuilder { @@ -31,6 +31,7 @@ impl Default for ExecutorBuilder { gas_limit: None, spec_id: SpecId::LATEST, use_zk: false, + legacy_assertions: false, } } } @@ -61,7 +62,7 @@ impl ExecutorBuilder { /// Sets the executor gas limit. #[inline] - pub fn gas_limit(mut self, gas_limit: U256) -> Self { + pub fn gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } @@ -73,19 +74,26 @@ impl ExecutorBuilder { self } + /// Sets the `legacy_assertions` flag. + #[inline] + pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { + self.legacy_assertions = legacy_assertions; + self + } + /// Builds the executor as configured. #[inline] pub fn build(self, env: Env, db: Backend) -> Executor { - let Self { mut stack, gas_limit, spec_id, use_zk } = self; - stack.block = Some(env.block.clone()); - stack.gas_price = Some(env.tx.gas_price); - let gas_limit = gas_limit.unwrap_or(env.block.gas_limit); - let mut exec = Executor::new( - db, - EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id), - stack.build(), - gas_limit, - ); + let Self { mut stack, gas_limit, spec_id, legacy_assertions, use_zk } = self; + if stack.block.is_none() { + stack.block = Some(env.block.clone()); + } + if stack.gas_price.is_none() { + stack.gas_price = Some(env.tx.gas_price); + } + let gas_limit = gas_limit.unwrap_or_else(|| env.block.gas_limit.saturating_to()); + let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id); + let mut exec = Executor::new(db, env, stack.build(), gas_limit, legacy_assertions); exec.use_zk = use_zk; exec } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index c55ebeb77..1655ef078 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,13 +1,11 @@ use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, Log, U256}; use eyre::Result; +use foundry_common::evm::Breakpoints; use foundry_config::FuzzConfig; -use foundry_evm_core::{ - constants::MAGIC_ASSUME, - decode::{decode_console_logs, RevertDecoder}, -}; +use foundry_evm_core::{constants::MAGIC_ASSUME, decode::RevertDecoder}; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, @@ -21,6 +19,25 @@ use std::cell::RefCell; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; +/// Contains data collected during fuzz test runs. +#[derive(Default)] +pub struct FuzzTestData { + // Stores the first fuzz case. + pub first_case: Option, + // Stored gas usage per fuzz case. + pub gas_by_case: Vec<(u64, u64)>, + // Stores the result and calldata of the last failed call, if any. + pub counterexample: (Bytes, RawCallResult), + // Stores up to `max_traces_to_collect` traces. + pub traces: Vec, + // Stores breakpoints for the last fuzz case. + pub breakpoints: Option, + // Stores coverage information for all fuzz cases. + pub coverage: Option, + // Stores logs for all fuzz cases + pub logs: Vec, +} + /// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`]. /// /// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with @@ -62,35 +79,19 @@ impl FuzzedExecutor { rd: &RevertDecoder, progress: Option<&ProgressBar>, ) -> FuzzTestResult { - // Stores the first Fuzzcase - let first_case: RefCell> = RefCell::default(); - - // gas usage per case - let gas_by_case: RefCell> = RefCell::default(); - - // Stores the result and calldata of the last failed call, if any. - let counterexample: RefCell<(Bytes, RawCallResult)> = RefCell::default(); - - // We want to collect at least one trace which will be displayed to user. - let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; - - // Stores up to `max_traces_to_collect` traces. - let traces: RefCell> = RefCell::default(); - - // Stores coverage information for all fuzz cases - let coverage: RefCell> = RefCell::default(); - + // Stores the fuzz test execution data. + let execution_data = RefCell::new(FuzzTestData::default()); let state = self.build_fuzz_state(); - - let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); let no_zksync_reserved_addresses = state.dictionary_read().no_zksync_reserved_addresses(); - + let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); let strat = proptest::prop_oneof![ 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures, no_zksync_reserved_addresses), dictionary_weight => fuzz_calldata_from_state(func.clone(), &state), ]; + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; + let show_logs = self.config.show_logs; - debug!(func=?func.name, should_fail, "fuzzing"); let run_result = self.runner.clone().run(&strat, |calldata| { let fuzz_res = self.single_fuzz(address, should_fail, calldata)?; @@ -101,19 +102,23 @@ impl FuzzedExecutor { match fuzz_res { FuzzOutcome::Case(case) => { - let mut first_case = first_case.borrow_mut(); - gas_by_case.borrow_mut().push((case.case.gas, case.case.stipend)); - if first_case.is_none() { - first_case.replace(case.case); + let mut data = execution_data.borrow_mut(); + data.gas_by_case.push((case.case.gas, case.case.stipend)); + if data.first_case.is_none() { + data.first_case.replace(case.case); } if let Some(call_traces) = case.traces { - if traces.borrow().len() == max_traces_to_collect { - traces.borrow_mut().pop(); + if data.traces.len() == max_traces_to_collect { + data.traces.pop(); } - traces.borrow_mut().push(call_traces); + data.traces.push(call_traces); + data.breakpoints.replace(case.breakpoints); } - - match &mut *coverage.borrow_mut() { + if show_logs { + data.logs.extend(case.logs); + } + // Collect and merge coverage if `forge snapshot` context. + match &mut data.coverage { Some(prev) => prev.merge(case.coverage.unwrap()), opt => *opt = case.coverage, } @@ -131,30 +136,36 @@ impl FuzzedExecutor { // to run at least one more case to find a minimal failure // case. let reason = rd.maybe_decode(&outcome.1.result, Some(status)); - *counterexample.borrow_mut() = outcome; + execution_data.borrow_mut().logs.extend(outcome.1.logs.clone()); + execution_data.borrow_mut().counterexample = outcome; // HACK: we have to use an empty string here to denote `None`. Err(TestCaseError::fail(reason.unwrap_or_default())) } } }); - let (calldata, call) = counterexample.into_inner(); + let fuzz_result = execution_data.into_inner(); + let (calldata, call) = fuzz_result.counterexample; - let mut traces = traces.into_inner(); - let last_run_traces = if run_result.is_ok() { traces.pop() } else { call.traces.clone() }; + let mut traces = fuzz_result.traces; + let (last_run_traces, last_run_breakpoints) = if run_result.is_ok() { + (traces.pop(), fuzz_result.breakpoints) + } else { + (call.traces.clone(), call.cheatcodes.map(|c| c.breakpoints)) + }; let mut result = FuzzTestResult { - first_case: first_case.take().unwrap_or_default(), - gas_by_case: gas_by_case.take(), + first_case: fuzz_result.first_case.unwrap_or_default(), + gas_by_case: fuzz_result.gas_by_case, success: run_result.is_ok(), reason: None, counterexample: None, - decoded_logs: decode_console_logs(&call.logs), - logs: call.logs, + logs: fuzz_result.logs, labeled_addresses: call.labels, traces: last_run_traces, + breakpoints: last_run_breakpoints, gas_report_traces: traces, - coverage: coverage.into_inner(), + coverage: fuzz_result.coverage, }; match run_result { @@ -220,12 +231,11 @@ impl FuzzedExecutor { case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, traces: call.traces, coverage: call.coverage, - debug: call.debug, breakpoints, + logs: call.logs, })) } else { Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { - debug: call.debug.clone(), exit_reason: call.exit_reason, counterexample: (calldata, call), breakpoints, diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs index b15cf3faa..7ec707eff 100644 --- a/crates/evm/evm/src/executors/fuzz/types.rs +++ b/crates/evm/evm/src/executors/fuzz/types.rs @@ -1,7 +1,6 @@ use crate::executors::RawCallResult; -use alloy_primitives::Bytes; +use alloy_primitives::{Bytes, Log}; use foundry_common::evm::Breakpoints; -use foundry_evm_core::debug::DebugArena; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::FuzzCase; use foundry_evm_traces::CallTraceArena; @@ -16,10 +15,10 @@ pub struct CaseOutcome { pub traces: Option, /// The coverage info collected during the call pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, /// Breakpoints char pc map pub breakpoints: Breakpoints, + /// logs of a single fuzz test case + pub logs: Vec, } /// Returned by a single fuzz when a counterexample has been discovered @@ -29,8 +28,6 @@ pub struct CounterExampleOutcome { pub counterexample: (Bytes, RawCallResult), /// The status of the call pub exit_reason: InstructionResult, - /// The debug nodes of the call - pub debug: Option, /// Breakpoints char pc map pub breakpoints: Breakpoints, } diff --git a/crates/evm/evm/src/executors/invariant/error.rs b/crates/evm/evm/src/executors/invariant/error.rs index 7c47ac0a4..b8cff9b1d 100644 --- a/crates/evm/evm/src/executors/invariant/error.rs +++ b/crates/evm/evm/src/executors/invariant/error.rs @@ -11,8 +11,6 @@ use proptest::test_runner::TestError; pub struct InvariantFailures { /// Total number of reverts. pub reverts: usize, - /// How many different invariants have been broken. - pub broken_invariants_count: usize, /// The latest revert reason of a run. pub revert_reason: Option, /// Maps a broken invariant to its specific error. diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 8811474a2..54517ebd7 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -7,8 +7,10 @@ use alloy_sol_types::{sol, SolCall}; use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::InvariantConfig; -use foundry_evm_core::constants::{ - CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, +use foundry_evm_core::{ + abi::HARDHAT_CONSOLE_ADDRESS, + constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, MAGIC_ASSUME}, + precompiles::PRECOMPILES, }; use foundry_evm_fuzz::{ invariant::{ @@ -32,6 +34,7 @@ use std::{cell::RefCell, collections::btree_map::Entry, sync::Arc}; mod error; pub use error::{InvariantFailures, InvariantFuzzError}; +use foundry_evm_coverage::HitMaps; mod replay; pub use replay::{replay_error, replay_run}; @@ -97,11 +100,152 @@ sol! { } } +/// Contains data collected during invariant test runs. +pub struct InvariantTestData { + // Consumed gas and calldata of every successful fuzz call. + pub fuzz_cases: Vec, + // Data related to reverts or failed assertions of the test. + pub failures: InvariantFailures, + // Calldata in the last invariant run. + pub last_run_inputs: Vec, + // Additional traces for gas report. + pub gas_report_traces: Vec>, + // Last call results of the invariant test. + pub last_call_results: Option, + // Coverage information collected from all fuzzed calls. + pub coverage: Option, + + // Proptest runner to query for random values. + // The strategy only comes with the first `input`. We fill the rest of the `inputs` + // until the desired `depth` so we can use the evolving fuzz dictionary + // during the run. + pub branch_runner: TestRunner, +} + +/// Contains invariant test data. +pub struct InvariantTest { + // Fuzz state of invariant test. + pub fuzz_state: EvmFuzzState, + // Contracts fuzzed by the invariant test. + pub targeted_contracts: FuzzRunIdentifiedContracts, + // Data collected during invariant runs. + pub execution_data: RefCell, +} + +impl InvariantTest { + /// Instantiates an invariant test. + pub fn new( + fuzz_state: EvmFuzzState, + targeted_contracts: FuzzRunIdentifiedContracts, + failures: InvariantFailures, + last_call_results: Option, + branch_runner: TestRunner, + ) -> Self { + let mut fuzz_cases = vec![]; + if last_call_results.is_none() { + fuzz_cases.push(FuzzedCases::new(vec![])); + } + let execution_data = RefCell::new(InvariantTestData { + fuzz_cases, + failures, + last_run_inputs: vec![], + gas_report_traces: vec![], + last_call_results, + coverage: None, + branch_runner, + }); + Self { fuzz_state, targeted_contracts, execution_data } + } + + /// Returns number of invariant test reverts. + pub fn reverts(&self) -> usize { + self.execution_data.borrow().failures.reverts + } + + /// Whether invariant test has errors or not. + pub fn has_errors(&self) -> bool { + self.execution_data.borrow().failures.error.is_some() + } + + /// Set invariant test error. + pub fn set_error(&self, error: InvariantFuzzError) { + self.execution_data.borrow_mut().failures.error = Some(error); + } + + /// Set last invariant test call results. + pub fn set_last_call_results(&self, call_result: Option) { + self.execution_data.borrow_mut().last_call_results = call_result; + } + + /// Set last invariant run call sequence. + pub fn set_last_run_inputs(&self, inputs: &Vec) { + self.execution_data.borrow_mut().last_run_inputs.clone_from(inputs); + } + + /// Merge current collected coverage with the new coverage from last fuzzed call. + pub fn merge_coverage(&self, new_coverage: Option) { + match &mut self.execution_data.borrow_mut().coverage { + Some(prev) => prev.merge(new_coverage.unwrap()), + opt => *opt = new_coverage, + } + } + + /// End invariant test run by collecting results, cleaning collected artifacts and reverting + /// created fuzz state. + pub fn end_run(&self, run: InvariantTestRun, gas_samples: usize) { + // We clear all the targeted contracts created during this run. + self.targeted_contracts.clear_created_contracts(run.created_contracts); + + let mut invariant_data = self.execution_data.borrow_mut(); + if invariant_data.gas_report_traces.len() < gas_samples { + invariant_data.gas_report_traces.push(run.run_traces); + } + invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs)); + + // Revert state to not persist values between runs. + self.fuzz_state.revert(); + } +} + +/// Contains data for an invariant test run. +pub struct InvariantTestRun { + // Invariant run call sequence. + pub inputs: Vec, + // Current invariant run executor. + pub executor: Executor, + // Invariant run stat reports (eg. gas usage). + pub fuzz_runs: Vec, + // Contracts created during current invariant run. + pub created_contracts: Vec
, + // Traces of each call of the invariant run call sequence. + pub run_traces: Vec, + // Current depth of invariant run. + pub depth: u32, + // Current assume rejects of the invariant run. + pub assume_rejects_counter: u32, +} + +impl InvariantTestRun { + /// Instantiates an invariant test run. + pub fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self { + Self { + inputs: vec![first_input], + executor, + fuzz_runs: Vec::with_capacity(depth), + created_contracts: vec![], + run_traces: vec![], + depth: 0, + assume_rejects_counter: 0, + } + } +} + /// Wrapper around any [`Executor`] implementor which provides fuzzing support using [`proptest`]. /// -/// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contracts with -/// inputs, until it finds a counterexample sequence. The provided [`TestRunner`] contains all the -/// configuration which can be overridden via [environment variables](proptest::test_runner::Config) +/// After instantiation, calling `invariant_fuzz` will proceed to hammer the deployed smart +/// contracts with inputs, until it finds a counterexample sequence. The provided [`TestRunner`] +/// contains all the configuration which can be overridden via [environment +/// variables](proptest::test_runner::Config) pub struct InvariantExecutor<'a> { pub executor: Executor, /// Proptest runner. @@ -148,73 +292,31 @@ impl<'a> InvariantExecutor<'a> { return Err(eyre!("Invariant test function should have no inputs")) } - let (fuzz_state, targeted_contracts, strat) = - self.prepare_fuzzing(&invariant_contract, fuzz_fixtures)?; + let (invariant_test, invariant_strategy) = + self.prepare_test(&invariant_contract, fuzz_fixtures)?; - // Stores the consumed gas and calldata of every successful fuzz call. - let fuzz_cases: RefCell> = RefCell::new(Default::default()); - - // Stores data related to reverts or failed assertions of the test. - let failures = RefCell::new(InvariantFailures::new()); - - // Stores the calldata in the last run. - let last_run_inputs: RefCell> = RefCell::new(vec![]); - - // Stores additional traces for gas report. - let gas_report_traces: RefCell>> = RefCell::default(); - - // Let's make sure the invariant is sound before actually starting the run: - // We'll assert the invariant in its initial state, and if it fails, we'll - // already know if we can early exit the invariant run. - // This does not count as a fuzz run. It will just register the revert. - let last_call_results = RefCell::new(assert_invariants( - &invariant_contract, - &self.config, - &targeted_contracts, - &self.executor, - &[], - &mut failures.borrow_mut(), - )?); - - if last_call_results.borrow().is_none() { - fuzz_cases.borrow_mut().push(FuzzedCases::new(vec![])); - } - - // The strategy only comes with the first `input`. We fill the rest of the `inputs` - // until the desired `depth` so we can use the evolving fuzz dictionary - // during the run. We need another proptest runner to query for random - // values. - let branch_runner = RefCell::new(self.runner.clone()); - let _ = self.runner.run(&strat, |first_input| { - let mut inputs = vec![first_input]; + let _ = self.runner.run(&invariant_strategy, |first_input| { + // Create current invariant run data. + let mut current_run = InvariantTestRun::new( + first_input, + // Before each run, we must reset the backend state. + self.executor.clone(), + self.config.depth as usize, + ); // We stop the run immediately if we have reverted, and `fail_on_revert` is set. - if self.config.fail_on_revert && failures.borrow().reverts > 0 { + if self.config.fail_on_revert && invariant_test.reverts() > 0 { return Err(TestCaseError::fail("Revert occurred.")) } - // Before each run, we must reset the backend state. - let mut executor = self.executor.clone(); - - // Used for stat reports (eg. gas usage). - let mut fuzz_runs = Vec::with_capacity(self.config.depth as usize); - - // Created contracts during a run. - let mut created_contracts = vec![]; - - // Traces of each call of the sequence. - let mut run_traces = Vec::new(); - - let mut current_run = 0; - let mut assume_rejects_counter = 0; - - while current_run < self.config.depth { - let tx = inputs.last().ok_or_else(|| { + while current_run.depth < self.config.depth { + let tx = current_run.inputs.last().ok_or_else(|| { TestCaseError::fail("No input generated to call fuzzed target.") })?; // Execute call from the randomly generated sequence and commit state changes. - let call_result = executor + let call_result = current_run + .executor .transact_raw( tx.sender, tx.call_details.target, @@ -225,43 +327,47 @@ impl<'a> InvariantExecutor<'a> { TestCaseError::fail(format!("Could not make raw evm call: {e}")) })?; + // Collect coverage from last fuzzed call. + invariant_test.merge_coverage(call_result.coverage.clone()); + if call_result.result.as_ref() == MAGIC_ASSUME { - inputs.pop(); - assume_rejects_counter += 1; - if assume_rejects_counter > self.config.max_assume_rejects { - failures.borrow_mut().error = Some(InvariantFuzzError::MaxAssumeRejects( + current_run.inputs.pop(); + current_run.assume_rejects_counter += 1; + if current_run.assume_rejects_counter > self.config.max_assume_rejects { + invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects( self.config.max_assume_rejects, )); return Err(TestCaseError::fail("Max number of vm.assume rejects reached.")) } } else { // Collect data for fuzzing from the state changeset. - let mut state_changeset = call_result.state_changeset.clone().unwrap(); + let mut state_changeset = call_result.state_changeset.clone(); if !call_result.reverted { collect_data( + &invariant_test, &mut state_changeset, - &targeted_contracts, tx, &call_result, - &fuzz_state, self.config.depth, ); } // Collect created contracts and add to fuzz targets only if targeted contracts // are updatable. - if let Err(error) = &targeted_contracts.collect_created_contracts( - &state_changeset, - self.project_contracts, - self.setup_contracts, - &self.artifact_filters, - &mut created_contracts, - ) { + if let Err(error) = + &invariant_test.targeted_contracts.collect_created_contracts( + &state_changeset, + self.project_contracts, + self.setup_contracts, + &self.artifact_filters, + &mut current_run.created_contracts, + ) + { warn!(target: "forge::test", "{error}"); } - fuzz_runs.push(FuzzCase { + current_run.fuzz_runs.push(FuzzCase { calldata: tx.call_details.calldata.clone(), gas: call_result.gas_used, stipend: call_result.stipend, @@ -269,62 +375,50 @@ impl<'a> InvariantExecutor<'a> { let result = can_continue( &invariant_contract, + &invariant_test, + &mut current_run, &self.config, call_result, - &executor, - &inputs, - &mut failures.borrow_mut(), - &targeted_contracts, &state_changeset, - &mut run_traces, ) .map_err(|e| TestCaseError::fail(e.to_string()))?; - if !result.can_continue || current_run == self.config.depth - 1 { - last_run_inputs.borrow_mut().clone_from(&inputs); + if !result.can_continue || current_run.depth == self.config.depth - 1 { + invariant_test.set_last_run_inputs(¤t_run.inputs); } + // If test cannot continue then stop current run and exit test suite. if !result.can_continue { - break + return Err(TestCaseError::fail("Test cannot continue.")) } - *last_call_results.borrow_mut() = result.call_result; - current_run += 1; + invariant_test.set_last_call_results(result.call_result); + current_run.depth += 1; } // Generates the next call from the run using the recently updated // dictionary. - inputs.push( - strat - .new_tree(&mut branch_runner.borrow_mut()) + current_run.inputs.push( + invariant_strategy + .new_tree(&mut invariant_test.execution_data.borrow_mut().branch_runner) .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? .current(), ); } // Call `afterInvariant` only if it is declared and test didn't fail already. - if invariant_contract.call_after_invariant && failures.borrow().error.is_none() { + if invariant_contract.call_after_invariant && !invariant_test.has_errors() { assert_after_invariant( &invariant_contract, + &invariant_test, + ¤t_run, &self.config, - &targeted_contracts, - &mut executor, - &mut failures.borrow_mut(), - &inputs, ) .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?; } - // We clear all the targeted contracts created during this run. - let _ = &targeted_contracts.clear_created_contracts(created_contracts); - - if gas_report_traces.borrow().len() < self.config.gas_report_samples as usize { - gas_report_traces.borrow_mut().push(run_traces); - } - fuzz_cases.borrow_mut().push(FuzzedCases::new(fuzz_runs)); - - // Revert state to not persist values between runs. - fuzz_state.revert(); + // End current invariant test run. + invariant_test.end_run(current_run, self.config.gas_report_samples as usize); // If running with progress then increment completed runs. if let Some(progress) = progress { @@ -335,29 +429,27 @@ impl<'a> InvariantExecutor<'a> { }); trace!(?fuzz_fixtures); - fuzz_state.log_stats(); - - let (reverts, error) = failures.into_inner().into_inner(); + invariant_test.fuzz_state.log_stats(); + let result = invariant_test.execution_data.into_inner(); Ok(InvariantFuzzTestResult { - error, - cases: fuzz_cases.into_inner(), - reverts, - last_run_inputs: last_run_inputs.into_inner(), - gas_report_traces: gas_report_traces.into_inner(), + error: result.failures.error, + cases: result.fuzz_cases, + reverts: result.failures.reverts, + last_run_inputs: result.last_run_inputs, + gas_report_traces: result.gas_report_traces, + coverage: result.coverage, }) } /// Prepares certain structures to execute the invariant tests: - /// * Fuzz dictionary - /// * Targeted contracts + /// * Invariant Fuzz Test. /// * Invariant Strategy - fn prepare_fuzzing( + fn prepare_test( &mut self, invariant_contract: &InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, - ) -> Result<(EvmFuzzState, FuzzRunIdentifiedContracts, impl Strategy)> - { + ) -> Result<(InvariantTest, impl Strategy)> { // Finds out the chosen deployed contracts and/or senders. self.select_contract_artifacts(invariant_contract.address)?; let (targeted_senders, targeted_contracts) = @@ -365,7 +457,7 @@ impl<'a> InvariantExecutor<'a> { // Stores fuzz state for use with [fuzz_calldata_from_state]. let fuzz_state = EvmFuzzState::new( - self.executor.backend.mem_db(), + self.executor.backend().mem_db(), self.config.dictionary, self.config.no_zksync_reserved_addresses, ); @@ -399,10 +491,36 @@ impl<'a> InvariantExecutor<'a> { )); } - self.executor.inspector.fuzzer = + self.executor.inspector_mut().fuzzer = Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true }); - Ok((fuzz_state, targeted_contracts, strat)) + // Let's make sure the invariant is sound before actually starting the run: + // We'll assert the invariant in its initial state, and if it fails, we'll + // already know if we can early exit the invariant run. + // This does not count as a fuzz run. It will just register the revert. + let mut failures = InvariantFailures::new(); + let last_call_results = assert_invariants( + invariant_contract, + &self.config, + &targeted_contracts, + &self.executor, + &[], + &mut failures, + )?; + if let Some(error) = failures.error { + return Err(eyre!(error.revert_reason().unwrap_or_default())) + } + + Ok(( + InvariantTest::new( + fuzz_state, + targeted_contracts, + failures, + last_call_results, + self.runner.clone(), + ), + strat, + )) } /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string @@ -514,6 +632,8 @@ impl<'a> InvariantExecutor<'a> { HARDHAT_CONSOLE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, ]); + // Extend with precompiles - https://github.com/foundry-rs/foundry/issues/4287 + excluded_senders.extend(PRECOMPILES); let sender_filters = SenderFilters::new(targeted_senders, excluded_senders); let selected = @@ -672,11 +792,10 @@ impl<'a> InvariantExecutor<'a> { /// before inserting it into the dictionary. Otherwise, we flood the dictionary with /// randomly generated addresses. fn collect_data( + invariant_test: &InvariantTest, state_changeset: &mut HashMap, - fuzzed_contracts: &FuzzRunIdentifiedContracts, tx: &BasicTxDetails, call_result: &RawCallResult, - fuzz_state: &EvmFuzzState, run_depth: u32, ) { // Verify it has no code. @@ -694,8 +813,8 @@ fn collect_data( } // Collect values from fuzzed call result and add them to fuzz dictionary. - fuzz_state.collect_values_from_call( - fuzzed_contracts, + invariant_test.fuzz_state.collect_values_from_call( + &invariant_test.targeted_contracts, tx, &call_result.result, &call_result.logs, diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index ef4194019..f5bf92652 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -12,7 +12,7 @@ use foundry_evm_fuzz::{ invariant::{BasicTxDetails, InvariantContract}, BaseCounterExample, }; -use foundry_evm_traces::{load_contracts, TraceKind, Traces}; +use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces}; use indicatif::ProgressBar; use parking_lot::RwLock; use proptest::test_runner::TestError; @@ -33,7 +33,9 @@ pub fn replay_run( inputs: &[BasicTxDetails], ) -> Result> { // We want traces for a failed case. - executor.set_tracing(true); + if executor.inspector().tracer.is_none() { + executor.set_tracing(TraceMode::Call); + } let mut counterexample_sequence = vec![]; @@ -142,7 +144,7 @@ pub fn replay_error( /// Sets up the calls generated by the internal fuzzer, if they exist. fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { - if let Some(fuzzer) = &mut executor.inspector.fuzzer { + if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer { if let Some(call_generator) = &mut fuzzer.call_generator { call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); call_generator.set_replay(true); diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs index 5ddcccaf0..c6a15930c 100644 --- a/crates/evm/evm/src/executors/invariant/result.rs +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -1,12 +1,13 @@ use super::{ call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, - InvariantFailures, InvariantFuzzError, + InvariantFailures, InvariantFuzzError, InvariantTest, InvariantTestRun, }; use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; use eyre::Result; use foundry_config::InvariantConfig; use foundry_evm_core::utils::StateChangeset; +use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract}, FuzzedCases, @@ -27,6 +28,8 @@ pub struct InvariantFuzzTestResult { pub last_run_inputs: Vec, /// Additional traces used for gas report construction. pub gas_report_traces: Vec>, + /// The coverage info collected during the invariant test runs. + pub coverage: Option, } /// Enriched results of an invariant run check. @@ -56,7 +59,7 @@ pub(crate) fn assert_invariants( ) -> Result> { let mut inner_sequence = vec![]; - if let Some(fuzzer) = &executor.inspector.fuzzer { + if let Some(fuzzer) = &executor.inspector().fuzzer { if let Some(call_generator) = &fuzzer.call_generator { inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); } @@ -86,64 +89,68 @@ pub(crate) fn assert_invariants( Ok(Some(call_result)) } -/// Verifies that the invariant run execution can continue. -/// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were -/// asserted. -#[allow(clippy::too_many_arguments)] +/// Returns if invariant test can continue and last successful call result of the invariant test +/// function (if it can continue). pub(crate) fn can_continue( invariant_contract: &InvariantContract<'_>, + invariant_test: &InvariantTest, + invariant_run: &mut InvariantTestRun, invariant_config: &InvariantConfig, call_result: RawCallResult, - executor: &Executor, - calldata: &[BasicTxDetails], - failures: &mut InvariantFailures, - targeted_contracts: &FuzzRunIdentifiedContracts, state_changeset: &StateChangeset, - run_traces: &mut Vec, ) -> Result { let mut call_results = None; let handlers_succeeded = || { - targeted_contracts.targets.lock().keys().all(|address| { - executor.is_success(*address, false, Cow::Borrowed(state_changeset), false) + invariant_test.targeted_contracts.targets.lock().keys().all(|address| { + invariant_run.executor.is_success( + *address, + false, + Cow::Borrowed(state_changeset), + false, + ) }) }; // Assert invariants if the call did not revert and the handlers did not fail. if !call_result.reverted && handlers_succeeded() { if let Some(traces) = call_result.traces { - run_traces.push(traces); + invariant_run.run_traces.push(traces); } call_results = assert_invariants( invariant_contract, invariant_config, - targeted_contracts, - executor, - calldata, - failures, + &invariant_test.targeted_contracts, + &invariant_run.executor, + &invariant_run.inputs, + &mut invariant_test.execution_data.borrow_mut().failures, )?; if call_results.is_none() { return Ok(RichInvariantResults::new(false, None)); } } else { // Increase the amount of reverts. - failures.reverts += 1; + let mut invariant_data = invariant_test.execution_data.borrow_mut(); + invariant_data.failures.reverts += 1; // If fail on revert is set, we must return immediately. if invariant_config.fail_on_revert { let case_data = FailedInvariantCaseData::new( invariant_contract, invariant_config, - targeted_contracts, - calldata, + &invariant_test.targeted_contracts, + &invariant_run.inputs, call_result, &[], ); - failures.revert_reason = Some(case_data.revert_reason.clone()); - let error = InvariantFuzzError::Revert(case_data); - failures.error = Some(error); + invariant_data.failures.revert_reason = Some(case_data.revert_reason.clone()); + invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data)); return Ok(RichInvariantResults::new(false, None)); + } else if call_result.reverted { + // If we don't fail test on revert then remove last reverted call from inputs. + // This improves shrinking performance as irrelevant calls won't be checked again. + invariant_run.inputs.pop(); } } Ok(RichInvariantResults::new(true, call_results)) @@ -153,25 +160,23 @@ pub(crate) fn can_continue( /// If call fails then the invariant test is considered failed. pub(crate) fn assert_after_invariant( invariant_contract: &InvariantContract<'_>, + invariant_test: &InvariantTest, + invariant_run: &InvariantTestRun, invariant_config: &InvariantConfig, - targeted_contracts: &FuzzRunIdentifiedContracts, - executor: &mut Executor, - invariant_failures: &mut InvariantFailures, - inputs: &[BasicTxDetails], ) -> Result { let (call_result, success) = - call_after_invariant_function(executor, invariant_contract.address)?; + call_after_invariant_function(&invariant_run.executor, invariant_contract.address)?; // Fail the test case if `afterInvariant` doesn't succeed. if !success { let case_data = FailedInvariantCaseData::new( invariant_contract, invariant_config, - targeted_contracts, - inputs, + &invariant_test.targeted_contracts, + &invariant_run.inputs, call_result, &[], ); - invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data)); + invariant_test.set_error(InvariantFuzzError::BrokenInvariant(case_data)); } Ok(success) } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 249f31a9e..0f67c332e 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -14,17 +14,16 @@ use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, Log, U256}; use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ - backend::{Backend, CowBackend, DatabaseError, DatabaseExt, DatabaseResult}, + backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt, GLOBAL_FAIL_SLOT}, constants::{ CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, - DEFAULT_CREATE2_DEPLOYER_CODE, + DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER, }, - debug::DebugArena, decode::RevertDecoder, utils::StateChangeset, }; use foundry_evm_coverage::HitMaps; -use foundry_evm_traces::CallTraceArena; +use foundry_evm_traces::{CallTraceArena, TraceMode}; use foundry_zksync_core::ZkTransactionMetadata; use itertools::Itertools; use revm::{ @@ -32,7 +31,7 @@ use revm::{ interpreter::{return_ok, InstructionResult}, primitives::{ BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, ResultAndState, - SpecId, TransactTo, TxEnv, + SpecId, TxEnv, TxKind, }, Database, }; @@ -47,8 +46,8 @@ pub use fuzz::FuzzedExecutor; pub mod invariant; pub use invariant::InvariantExecutor; -mod tracing; -pub use tracing::TracingExecutor; +mod trace; +pub use trace::TracingExecutor; sol! { interface ITest { @@ -83,7 +82,9 @@ pub struct Executor { /// The gas limit for calls and deployments. This is different from the gas limit imposed by /// the passed in environment, as those limits are used by the EVM for certain opcodes like /// `gaslimit`. - gas_limit: U256, + gas_limit: u64, + /// Whether `failed()` should be called on the test contract to determine if the test failed. + legacy_assertions: bool, /// Sets up the next transaction to be executed as a ZK transaction. zk_tx: Option, @@ -94,16 +95,23 @@ pub struct Executor { } impl Executor { + /// Creates a new `ExecutorBuilder`. + #[inline] + pub fn builder() -> ExecutorBuilder { + ExecutorBuilder::new() + } + /// Creates a new `Executor` with the given arguments. #[inline] pub fn new( mut backend: Backend, env: EnvWithHandlerCfg, inspector: InspectorStack, - gas_limit: U256, + gas_limit: u64, + legacy_assertions: bool, ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks - // does not fail + // do not fail. backend.insert_account_info( CHEATCODE_ADDRESS, revm::primitives::AccountInfo { @@ -120,33 +128,69 @@ impl Executor { env, inspector, gas_limit, + legacy_assertions, zk_tx: None, zk_persisted_factory_deps: Default::default(), use_zk: false, } } - /// Returns the spec ID of the executor. + fn clone_with_backend(&self, backend: Backend) -> Self { + let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(self.env().clone()), self.spec_id()); + Self::new(backend, env, self.inspector().clone(), self.gas_limit, self.legacy_assertions) + } + + /// Returns a reference to the EVM backend. + pub fn backend(&self) -> &Backend { + &self.backend + } + + /// Returns a mutable reference to the EVM backend. + pub fn backend_mut(&mut self) -> &mut Backend { + &mut self.backend + } + + /// Returns a reference to the EVM environment. + pub fn env(&self) -> &Env { + &self.env.env + } + + /// Returns a mutable reference to the EVM environment. + pub fn env_mut(&mut self) -> &mut Env { + &mut self.env.env + } + + /// Returns a reference to the EVM inspector. + pub fn inspector(&self) -> &InspectorStack { + &self.inspector + } + + /// Returns a mutable reference to the EVM inspector. + pub fn inspector_mut(&mut self) -> &mut InspectorStack { + &mut self.inspector + } + + /// Returns the EVM spec ID. pub fn spec_id(&self) -> SpecId { - self.env.handler_cfg.spec_id + self.env.spec_id() } /// Creates the default CREATE2 Contract Deployer for local tests and scripts. pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> { trace!("deploying local create2 deployer"); let create2_deployer_account = self - .backend + .backend() .basic_ref(DEFAULT_CREATE2_DEPLOYER)? - .ok_or_else(|| DatabaseError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; + .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; - // if the deployer is not currently deployed, deploy the default one + // If the deployer is not currently deployed, deploy the default one. if create2_deployer_account.code.map_or(true, |code| code.is_empty()) { - let creator = "0x3fAB184622Dc19b6109349B94811493BF2a45362".parse().unwrap(); + let creator = DEFAULT_CREATE2_DEPLOYER_DEPLOYER; // Probably 0, but just in case. let initial_balance = self.get_balance(creator)?; - self.set_balance(creator, U256::MAX)?; + let res = self.deploy(creator, DEFAULT_CREATE2_DEPLOYER_CODE.into(), U256::ZERO, None)?; trace!(create2=?res.address, "deployed local create2 deployer"); @@ -157,30 +201,29 @@ impl Executor { } /// Set the balance of an account. - pub fn set_balance(&mut self, address: Address, amount: U256) -> DatabaseResult<&mut Self> { + pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { trace!(?address, ?amount, "setting account balance ZK={}", self.use_zk); - let mut account = self.backend.basic_ref(address)?.unwrap_or_default(); + let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); account.balance = amount; - self.backend.insert_account_info(address, account); + self.backend_mut().insert_account_info(address, account); if self.use_zk { let (address, slot) = foundry_zksync_core::state::get_balance_storage(address); self.backend.insert_account_storage(address, slot, amount)?; } - Ok(self) + Ok(()) } /// Gets the balance of an account - pub fn get_balance(&self, address: Address) -> DatabaseResult { - Ok(self.backend.basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default()) + pub fn get_balance(&self, address: Address) -> BackendResult { + Ok(self.backend().basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default()) } /// Set the nonce of an account. - pub fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<&mut Self> { - let mut account = self.backend.basic_ref(address)?.unwrap_or_default(); + pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> { + let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; - self.backend.insert_account_info(address, account); - + self.backend_mut().insert_account_info(address, account); if self.use_zk { let (address, slot) = foundry_zksync_core::state::get_nonce_storage(address); // fetch the full nonce to preserve account's deployment nonce @@ -190,40 +233,33 @@ impl Executor { foundry_zksync_core::state::new_full_nonce(nonce, full_nonce.deploy_nonce); self.backend.insert_account_storage(address, slot, new_full_nonce)?; } - - Ok(self) + Ok(()) } /// Returns the nonce of an account. - pub fn get_nonce(&self, address: Address) -> DatabaseResult { - Ok(self.backend.basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default()) + pub fn get_nonce(&self, address: Address) -> BackendResult { + Ok(self.backend().basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default()) } /// Returns `true` if the account has no code. - pub fn is_empty_code(&self, address: Address) -> DatabaseResult { - Ok(self.backend.basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true)) + pub fn is_empty_code(&self, address: Address) -> BackendResult { + Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true)) } #[inline] - pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { - self.inspector.tracing(tracing); - self - } - - #[inline] - pub fn set_debugger(&mut self, debugger: bool) -> &mut Self { - self.inspector.enable_debugger(debugger); + pub fn set_tracing(&mut self, mode: TraceMode) -> &mut Self { + self.inspector_mut().tracing(mode); self } #[inline] pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self { - self.inspector.print(trace_printer); + self.inspector_mut().print(trace_printer); self } #[inline] - pub fn set_gas_limit(&mut self, gas_limit: U256) -> &mut Self { + pub fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self { self.gas_limit = gas_limit; self } @@ -239,7 +275,7 @@ impl Executor { value: U256, rd: Option<&RevertDecoder>, ) -> Result { - let env = self.build_test_env(from, TransactTo::Create, code, value); + let env = self.build_test_env(from, TxKind::Create, code, value); self.deploy_with_env(env, rd) } @@ -248,14 +284,15 @@ impl Executor { /// /// # Panics /// - /// Panics if `env.tx.transact_to` is not `TransactTo::Create(_)`. + /// Panics if `env.tx.transact_to` is not `TxKind::Create(_)`. + #[instrument(name = "deploy", level = "debug", skip_all)] pub fn deploy_with_env( &mut self, env: EnvWithHandlerCfg, rd: Option<&RevertDecoder>, ) -> Result { assert!( - matches!(env.tx.transact_to, TransactTo::Create), + matches!(env.tx.transact_to, TxKind::Create), "Expected create transaction, got {:?}", env.tx.transact_to ); @@ -269,7 +306,7 @@ impl Executor { // also mark this library as persistent, this will ensure that the state of the library is // persistent across fork swaps in forking mode - self.backend.add_persistent_account(address); + self.backend_mut().add_persistent_account(address); debug!(%address, "deployed contract"); @@ -282,6 +319,7 @@ impl Executor { /// /// Ayn changes made during the setup call to env's block environment are persistent, for /// example `vm.chainId()` will change the `block.chainId` for all subsequent test calls. + #[instrument(name = "setup", level = "debug", skip_all)] pub fn setup( &mut self, from: Option
, @@ -291,21 +329,20 @@ impl Executor { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); - self.backend.set_test_contract(to).set_caller(from); + self.backend_mut().set_test_contract(to).set_caller(from); let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR); let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?; res = res.into_result(rd)?; // record any changes made to the block's environment during setup - self.env.block = res.env.block.clone(); + self.env_mut().block = res.env.block.clone(); // and also the chainid, which can be set manually - self.env.cfg.chain_id = res.env.cfg.chain_id; + self.env_mut().cfg.chain_id = res.env.cfg.chain_id; - if let Some(changeset) = &res.state_changeset { - let success = self.is_raw_call_success(to, Cow::Borrowed(changeset), &res, false); - if !success { - return Err(res.into_execution_error("execution error".to_string()).into()); - } + let success = + self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false); + if !success { + return Err(res.into_execution_error("execution error".to_string()).into()); } Ok(res) @@ -364,7 +401,7 @@ impl Executor { calldata: Bytes, value: U256, ) -> eyre::Result { - let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); + let env = self.build_test_env(from, TxKind::Call(to), calldata, value); self.call_with_env(env) } @@ -376,16 +413,17 @@ impl Executor { calldata: Bytes, value: U256, ) -> eyre::Result { - let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); + let env = self.build_test_env(from, TxKind::Call(to), calldata, value); self.transact_with_env(env) } /// Execute the transaction configured in `env.tx`. /// /// The state after the call is **not** persisted. + #[instrument(name = "call", level = "debug", skip_all)] pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result { - let mut inspector = self.inspector.clone(); - let mut backend = CowBackend::new(&self.backend); + let mut inspector = self.inspector().clone(); + let mut backend = CowBackend::new_borrowed(self.backend()); let result = match &self.zk_tx { None => backend.inspect(&mut env, &mut inspector)?, Some(zk_tx) => { @@ -404,6 +442,7 @@ impl Executor { } /// Execute the transaction configured in `env.tx`. + #[instrument(name = "transact", level = "debug", skip_all)] pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector.clone(); let backend = &mut self.backend; @@ -429,7 +468,7 @@ impl Executor { result_and_state.clone(), backend.has_snapshot_failure(), )?; - let state = result_and_state.state.clone(); + let state = result_and_state.state; if let Some(traces) = &mut result.traces { for trace_node in traces.nodes() { if let Some(account_info) = state.get(&trace_node.trace.address) { @@ -449,28 +488,26 @@ impl Executor { /// the executed call result. /// /// This should not be exposed to the user, as it should be called only by `transact*`. + #[instrument(name = "commit", level = "debug", skip_all)] fn commit(&mut self, result: &mut RawCallResult) { // Persist changes to db. - if let Some(changes) = &result.state_changeset { - self.backend.commit(changes.clone()); - } + self.backend_mut().commit(result.state_changeset.clone()); // Persist cheatcode state. - self.inspector.cheatcodes = result.cheatcodes.take(); - if let Some(cheats) = self.inspector.cheatcodes.as_mut() { + self.inspector_mut().cheatcodes = result.cheatcodes.take(); + if let Some(cheats) = self.inspector_mut().cheatcodes.as_mut() { // Clear broadcastable transactions cheats.broadcastable_transactions.clear(); - debug!(target: "evm::executors", "cleared broadcastable transactions"); // corrected_nonce value is needed outside of this context (setUp), so we don't // reset it. } // Persist the changed environment. - self.inspector.set_env(&result.env); + self.inspector_mut().set_env(&result.env); } - /// Checks if a call to a test contract was successful. + /// Returns `true` if a test can be considered successful. /// /// This is the same as [`Self::is_success`], but will consume the `state_changeset` map to use /// internally when calling `failed()`. @@ -482,26 +519,15 @@ impl Executor { ) -> bool { self.is_raw_call_success( address, - Cow::Owned(call_result.state_changeset.take().unwrap_or_default()), + Cow::Owned(std::mem::take(&mut call_result.state_changeset)), call_result, should_fail, ) } - /// Checks if a call to a test contract was successful. - /// - /// This is the same as [`Self::is_success`] but intended for outcomes of [`Self::call_raw`]. - /// - /// ## Background - /// - /// Executing and failure checking `Executor::is_success` are two steps, for ds-test - /// legacy reasons failures can be stored in a global variables and needs to be called via a - /// solidity call `failed()(bool)`. + /// Returns `true` if a test can be considered successful. /// - /// Snapshots make this task more complicated because now we also need to keep track of that - /// global variable when we revert to a snapshot (because it is stored in state). Now, the - /// problem is that the `CowBackend` is dropped after every call, so we need to keep track - /// of the snapshot failure in the [`RawCallResult`] instead. + /// This is the same as [`Self::is_success`], but intended for outcomes of [`Self::call_raw`]. pub fn is_raw_call_success( &self, address: Address, @@ -516,21 +542,27 @@ impl Executor { self.is_success(address, call_result.reverted, state_changeset, should_fail) } - /// Check if a call to a test contract was successful. + /// Returns `true` if a test can be considered successful. /// - /// This function checks both the VM status of the call, DSTest's `failed` status and the - /// `globalFailed` flag which is stored in `failed` inside the `CHEATCODE_ADDRESS` contract. + /// If the call succeeded, we also have to check the global and local failure flags. /// - /// DSTest will not revert inside its `assertEq`-like functions which allows - /// to test multiple assertions in 1 test function while also preserving logs. + /// These are set by the test contract itself when an assertion fails, using the internal `fail` + /// function. The global flag is located in [`CHEATCODE_ADDRESS`] at slot [`GLOBAL_FAIL_SLOT`], + /// and the local flag is located in the test contract at an unspecified slot. /// - /// If an `assert` is violated, the contract's `failed` variable is set to true, and the - /// `globalFailure` flag inside the `CHEATCODE_ADDRESS` is also set to true, this way, failing - /// asserts from any contract are tracked as well. + /// This behavior is inherited from Dapptools, where initially only a public + /// `failed` variable was used to track test failures, and later, a global failure flag was + /// introduced to track failures across multiple contracts in + /// [ds-test#30](https://github.com/dapphub/ds-test/pull/30). /// - /// In order to check whether a test failed, we therefore need to evaluate the contract's - /// `failed` variable and the `globalFailure` flag, which happens by calling - /// `contract.failed()`. + /// The assumption is that the test runner calls `failed` on the test contract to determine if + /// it failed. However, we want to avoid this as much as possible, as it is relatively + /// expensive to set up an EVM call just for checking a single boolean flag. + /// + /// See: + /// - Newer DSTest: + /// - Older DSTest: + /// - forge-std: pub fn is_success( &self, address: Address, @@ -542,26 +574,50 @@ impl Executor { should_fail ^ success } + #[instrument(name = "is_success", level = "debug", skip_all)] fn is_success_raw( &self, address: Address, reverted: bool, state_changeset: Cow<'_, StateChangeset>, ) -> bool { - if self.backend.has_snapshot_failure() { - // a failure occurred in a reverted snapshot, which is considered a failed test + // The call reverted. + if reverted { + return false; + } + + // A failure occurred in a reverted snapshot, which is considered a failed test. + if self.backend().has_snapshot_failure() { return false; } - let mut success = !reverted; - if success { + // Check the global failure slot. + if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS) { + if let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT) { + if !failed_slot.present_value().is_zero() { + return false; + } + } + } + if let Ok(failed_slot) = self.backend().storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT) { + if !failed_slot.is_zero() { + return false; + } + } + + if !self.legacy_assertions { + return true; + } + + // Finally, resort to calling `DSTest::failed`. + { // Construct a new bare-bones backend to evaluate success. - let mut backend = self.backend.clone_empty(); + let mut backend = self.backend().clone_empty(); // We only clone the test contract and cheatcode accounts, // that's all we need to evaluate success. for address in [address, CHEATCODE_ADDRESS] { - let Ok(acc) = self.backend.basic_ref(address) else { return false }; + let Ok(acc) = self.backend().basic_ref(address) else { return false }; backend.insert_account_info(address, acc.unwrap_or_default()); } @@ -572,20 +628,19 @@ impl Executor { backend.commit(state_changeset.into_owned()); // Check if a DSTest assertion failed - let executor = - Self::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); + let executor = self.clone_with_backend(backend); let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None); match call { Ok(CallResult { raw: _, decoded_result: ITest::failedReturn { failed } }) => { - debug!(failed, "DSTest::failed()"); - success = !failed; + trace!(failed, "DSTest::failed()"); + !failed } Err(err) => { - debug!(%err, "failed to call DSTest::failed()"); + trace!(%err, "failed to call DSTest::failed()"); + true } } } - success } pub fn setup_zk_tx(&mut self, zk_tx: ZkTransactionMetadata) { @@ -599,19 +654,19 @@ impl Executor { fn build_test_env( &self, caller: Address, - transact_to: TransactTo, + transact_to: TxKind, data: Bytes, value: U256, ) -> EnvWithHandlerCfg { let env = Env { - cfg: self.env.cfg.clone(), + cfg: self.env().cfg.clone(), // We always set the gas price to 0 so we can execute the transaction regardless of // network conditions - the actual gas price is kept in `self.block` and is applied by // the cheatcode handler if it is enabled block: BlockEnv { basefee: U256::ZERO, - gas_limit: self.gas_limit, - ..self.env.block.clone() + gas_limit: U256::from(self.gas_limit), + ..self.env().block.clone() }, tx: TxEnv { caller, @@ -621,12 +676,12 @@ impl Executor { // As above, we set the gas price to 0. gas_price: U256::ZERO, gas_priority_fee: None, - gas_limit: self.gas_limit.to(), - ..self.env.tx.clone() + gas_limit: self.gas_limit, + ..self.env().tx.clone() }, }; - EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.env.handler_cfg.spec_id) + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.spec_id()) } } @@ -737,12 +792,10 @@ pub struct RawCallResult { pub traces: Option, /// The coverage info collected during the call pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, /// Scripted transactions generated from this call pub transactions: Option, /// The changeset of the state. - pub state_changeset: Option, + pub state_changeset: StateChangeset, /// The `revm::Env` after the call pub env: EnvWithHandlerCfg, /// The cheatcode states after execution @@ -770,9 +823,8 @@ impl Default for RawCallResult { labels: HashMap::new(), traces: None, coverage: None, - debug: None, transactions: None, - state_changeset: None, + state_changeset: HashMap::default(), env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), cheatcodes: Default::default(), out: None, @@ -856,25 +908,27 @@ impl std::ops::DerefMut for CallResult { fn convert_executed_result( env: EnvWithHandlerCfg, inspector: InspectorStack, - result: ResultAndState, + ResultAndState { result, state: state_changeset }: ResultAndState, has_snapshot_failure: bool, ) -> eyre::Result { - let ResultAndState { result: exec_result, state: state_changeset } = result; - let (exit_reason, gas_refunded, gas_used, out) = match exec_result { - ExecutionResult::Success { reason, gas_used, gas_refunded, output, .. } => { - (reason.into(), gas_refunded, gas_used, Some(output)) + let (exit_reason, gas_refunded, gas_used, out, _exec_logs) = match result { + ExecutionResult::Success { reason, gas_used, gas_refunded, output, logs, .. } => { + (reason.into(), gas_refunded, gas_used, Some(output), logs) } ExecutionResult::Revert { gas_used, output } => { // Need to fetch the unused gas - (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output))) + (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output)), vec![]) + } + ExecutionResult::Halt { reason, gas_used } => { + (reason.into(), 0_u64, gas_used, None, vec![]) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), 0_u64, gas_used, None), }; let stipend = revm::interpreter::gas::validate_initial_tx_gas( env.spec_id(), &env.tx.data, env.tx.transact_to.is_create(), &env.tx.access_list, + 0, ); let result = match &out { @@ -884,7 +938,7 @@ fn convert_executed_result( let combined_logs = inspector.cheatcodes.as_ref().map(|cheatcodes| cheatcodes.combined_logs.clone()); - let InspectorData { mut logs, labels, traces, coverage, debug, cheatcodes, chisel_state } = + let InspectorData { mut logs, labels, traces, coverage, cheatcodes, chisel_state } = inspector.collect(); let logs = match combined_logs { @@ -908,12 +962,10 @@ fn convert_executed_result( None => logs, }; - let transactions = match cheatcodes.as_ref() { - Some(cheats) if !cheats.broadcastable_transactions.is_empty() => { - Some(cheats.broadcastable_transactions.clone()) - } - _ => None, - }; + let transactions = cheatcodes + .as_ref() + .map(|c| c.broadcastable_transactions.clone()) + .filter(|txs| !txs.is_empty()); Ok(RawCallResult { deployments: HashMap::new(), @@ -928,9 +980,8 @@ fn convert_executed_result( labels, traces, coverage, - debug, transactions, - state_changeset: Some(state_changeset), + state_changeset, env, cheatcodes, out, diff --git a/crates/evm/evm/src/executors/tracing.rs b/crates/evm/evm/src/executors/trace.rs similarity index 83% rename from crates/evm/evm/src/executors/tracing.rs rename to crates/evm/evm/src/executors/trace.rs index 08c5d92ef..cc3e68776 100644 --- a/crates/evm/evm/src/executors/tracing.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -2,6 +2,7 @@ use crate::executors::{Executor, ExecutorBuilder}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; +use foundry_evm_traces::{InternalTraceMode, TraceMode}; use revm::primitives::{Env, SpecId}; use std::ops::{Deref, DerefMut}; @@ -16,13 +17,20 @@ impl TracingExecutor { fork: Option, version: Option, debug: bool, + decode_internal: bool, ) -> Self { let db = Backend::spawn(fork); + let trace_mode = + TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { + InternalTraceMode::Full + } else { + InternalTraceMode::None + }); Self { // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction executor: ExecutorBuilder::new() - .inspectors(|stack| stack.trace(true).debug(debug)) + .inspectors(|stack| stack.trace_mode(trace_mode)) .spec(evm_spec_id(&version.unwrap_or_default())) .build(env, db), } diff --git a/crates/evm/evm/src/inspectors/debugger.rs b/crates/evm/evm/src/inspectors/debugger.rs deleted file mode 100644 index c970cd671..000000000 --- a/crates/evm/evm/src/inspectors/debugger.rs +++ /dev/null @@ -1,150 +0,0 @@ -use alloy_primitives::Address; -use arrayvec::ArrayVec; -use foundry_common::ErrorExt; -use foundry_evm_core::{ - backend::DatabaseExt, - debug::{DebugArena, DebugNode, DebugStep}, - utils::gas_used, -}; -use revm::{ - interpreter::{ - opcode, CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, - Interpreter, InterpreterResult, - }, - EvmContext, Inspector, -}; -use revm_inspectors::tracing::types::CallKind; - -/// An inspector that collects debug nodes on every step of the interpreter. -#[derive(Clone, Debug, Default)] -pub struct Debugger { - /// The arena of [DebugNode]s - pub arena: DebugArena, - /// The ID of the current [DebugNode]. - pub head: usize, - /// The current execution address. - pub context: Address, -} - -impl Debugger { - /// Enters a new execution context. - pub fn enter(&mut self, depth: usize, address: Address, kind: CallKind) { - self.context = address; - self.head = self.arena.push_node(DebugNode { depth, address, kind, ..Default::default() }); - } - - /// Exits the current execution context, replacing it with the previous one. - pub fn exit(&mut self) { - if let Some(parent_id) = self.arena.arena[self.head].parent { - let DebugNode { depth, address, kind, .. } = self.arena.arena[parent_id]; - self.enter(depth, address, kind); - } - } -} - -impl Inspector for Debugger { - fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { - let pc = interp.program_counter(); - let op = interp.current_opcode(); - - // Extract the push bytes - let push_size = if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { - (op - opcode::PUSH0) as usize - } else { - 0 - }; - let push_bytes = (push_size > 0).then(|| { - let start = pc + 1; - let end = start + push_size; - let slice = &interp.contract.bytecode.bytecode()[start..end]; - debug_assert!(slice.len() <= 32); - let mut array = ArrayVec::new(); - array.try_extend_from_slice(slice).unwrap(); - array - }); - - let total_gas_used = gas_used( - ecx.spec_id(), - interp.gas.limit().saturating_sub(interp.gas.remaining()), - interp.gas.refunded() as u64, - ); - - // Reuse the memory from the previous step if the previous opcode did not modify it. - let memory = self.arena.arena[self.head] - .steps - .last() - .filter(|step| !step.opcode_modifies_memory()) - .map(|step| step.memory.clone()) - .unwrap_or_else(|| interp.shared_memory.context_memory().to_vec().into()); - - self.arena.arena[self.head].steps.push(DebugStep { - pc, - stack: interp.stack().data().clone(), - memory, - calldata: interp.contract().input.clone(), - returndata: interp.return_data_buffer.clone(), - instruction: op, - push_bytes: push_bytes.unwrap_or_default(), - total_gas_used, - }); - } - - fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { - self.enter( - ecx.journaled_state.depth() as usize, - inputs.bytecode_address, - inputs.scheme.into(), - ); - - None - } - - fn call_end( - &mut self, - _context: &mut EvmContext, - _inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { - self.exit(); - - outcome - } - - fn create( - &mut self, - ecx: &mut EvmContext, - inputs: &mut CreateInputs, - ) -> Option { - if let Err(err) = ecx.load_account(inputs.caller) { - let gas = Gas::new(inputs.gas_limit); - return Some(CreateOutcome::new( - InterpreterResult { - result: InstructionResult::Revert, - output: err.abi_encode_revert(), - gas, - }, - None, - )); - } - - let nonce = ecx.journaled_state.account(inputs.caller).info.nonce; - self.enter( - ecx.journaled_state.depth() as usize, - inputs.created_address(nonce), - CallKind::Create, - ); - - None - } - - fn create_end( - &mut self, - _context: &mut EvmContext, - _inputs: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - self.exit(); - - outcome - } -} diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index d26420187..631c4af81 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,12 +1,14 @@ use alloy_primitives::{Bytes, Log}; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; -use foundry_common::{ - console::{patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS}, - fmt::ConsoleFmt, - ErrorExt, +use foundry_common::{fmt::ConsoleFmt, ErrorExt}; +use foundry_evm_core::{ + abi::{patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS}, + InspectorExt, }; use revm::{ - interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult}, + interpreter::{ + CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, + }, Database, EvmContext, Inspector, }; @@ -38,7 +40,7 @@ impl LogCollector { } impl Inspector for LogCollector { - fn log(&mut self, _context: &mut EvmContext, log: &Log) { + fn log(&mut self, _interp: &mut Interpreter, _context: &mut EvmContext, log: &Log) { self.logs.push(log.clone()); } @@ -66,6 +68,16 @@ impl Inspector for LogCollector { } } +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(), + )); + } +} + /// Converts a call to Hardhat's `console.log` to a DSTest `log(string)` event. fn convert_hh_log_to_event(call: HardhatConsole::HardhatConsoleCalls) -> Log { // Convert the parameters of the call to their string representation using `ConsoleFmt`. diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index 786786b28..41008397a 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -10,9 +10,6 @@ pub use revm_inspectors::access_list::AccessListInspector; mod chisel_state; pub use chisel_state::ChiselState; -mod debugger; -pub use debugger::Debugger; - mod logs; pub use logs::LogCollector; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 85e6675a4..b1f28215b 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,25 +1,31 @@ use super::{ - Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Debugger, Fuzzer, LogCollector, - StackSnapshotType, TracingInspector, TracingInspectorConfig, + Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Fuzzer, LogCollector, + TracingInspector, }; -use alloy_primitives::{Address, Bytes, Log, U256}; +use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; +use foundry_cheatcodes::CheatcodesExecutor; use foundry_evm_core::{ backend::{update_state, DatabaseExt}, - debug::DebugArena, InspectorExt, }; use foundry_evm_coverage::HitMaps; -use foundry_evm_traces::CallTraceArena; +use foundry_evm_traces::{CallTraceArena, TraceMode}; use revm::{ inspectors::CustomPrintTracer, interpreter::{ - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, - Interpreter, InterpreterResult, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, + EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, }, - primitives::{BlockEnv, Env, EnvWithHandlerCfg, ExecutionResult, Output, TransactTo}, - DatabaseCommit, EvmContext, Inspector, + primitives::{ + BlockEnv, CreateScheme, Env, EnvWithHandlerCfg, ExecutionResult, Output, TransactTo, + }, + EvmContext, Inspector, +}; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + sync::Arc, }; -use std::{collections::HashMap, sync::Arc}; #[derive(Clone, Debug, Default)] #[must_use = "builders do nothing unless you call `build` on them"] @@ -39,9 +45,7 @@ pub struct InspectorStackBuilder { /// The fuzzer inspector and its state, if it exists. pub fuzzer: Option, /// Whether to enable tracing. - pub trace: Option, - /// Whether to enable the debugger. - pub debug: Option, + pub trace_mode: TraceMode, /// Whether logs should be collected. pub logs: Option, /// Whether coverage info should be collected. @@ -112,13 +116,6 @@ impl InspectorStackBuilder { self } - /// Set whether to enable the debugger. - #[inline] - pub fn debug(mut self, yes: bool) -> Self { - self.debug = Some(yes); - self - } - /// Set whether to enable the trace printer. #[inline] pub fn print(mut self, yes: bool) -> Self { @@ -128,8 +125,10 @@ impl InspectorStackBuilder { /// Set whether to enable the tracer. #[inline] - pub fn trace(mut self, yes: bool) -> Self { - self.trace = Some(yes); + pub fn trace_mode(mut self, mode: TraceMode) -> Self { + if self.trace_mode < mode { + self.trace_mode = mode + } self } @@ -148,8 +147,7 @@ impl InspectorStackBuilder { gas_price, cheatcodes, fuzzer, - trace, - debug, + trace_mode, logs, coverage, print, @@ -170,9 +168,8 @@ impl InspectorStackBuilder { } stack.collect_coverage(coverage.unwrap_or(false)); stack.collect_logs(logs.unwrap_or(true)); - stack.enable_debugger(debug.unwrap_or(false)); stack.print(print.unwrap_or(false)); - stack.tracing(trace.unwrap_or(false)); + stack.tracing(trace_mode); stack.enable_isolation(enable_isolation); @@ -195,45 +192,40 @@ macro_rules! call_inspectors { ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { $( if let Some($id) = $inspector { - $call + ({ #[inline(always)] #[cold] || $call })(); } )+ - } + }; + (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { + $( + if let Some($id) = $inspector { + if let Some(result) = ({ #[inline(always)] #[cold] || $call })() { + return result; + } + } + )+ + }; } -/// Same as [call_inspectors] macro, but with depth adjustment for isolated execution. +/// Same as [`call_inspectors!`], but with depth adjustment for isolated execution. macro_rules! call_inspectors_adjust_depth { - (#[no_ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + $data.journaled_state.depth += $self.in_inner_context as usize; + call_inspectors!([$($inspector),+], |$id| $call); + $data.journaled_state.depth -= $self.in_inner_context as usize; + }; + (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { $data.journaled_state.depth += $self.in_inner_context as usize; $( if let Some($id) = $inspector { - $call + if let Some(result) = ({ #[inline(always)] #[cold] || $call })() { + $data.journaled_state.depth -= $self.in_inner_context as usize; + return result; + } } )+ $data.journaled_state.depth -= $self.in_inner_context as usize; }; - ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { - if $self.in_inner_context { - $data.journaled_state.depth += 1; - $( - if let Some($id) = $inspector { - if let Some(result) = $call { - $data.journaled_state.depth -= 1; - return result; - } - } - )+ - $data.journaled_state.depth -= 1; - } else { - $( - if let Some($id) = $inspector { - if let Some(result) = $call { - return result; - } - } - )+ - } - }; } /// The collected results of [`InspectorStack`]. @@ -241,7 +233,6 @@ pub struct InspectorData { pub logs: Vec, pub labels: HashMap, pub traces: Option, - pub debug: Option, pub coverage: Option, pub cheatcodes: Option, pub chisel_state: Option<(Vec, Vec, InstructionResult)>, @@ -269,12 +260,25 @@ pub struct InnerContextData { /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or /// equivalent) the remaining inspectors are not called. +/// +/// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling +/// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives +/// us ability to create and execute separate EVM frames from inside cheatcodes while still having +/// access to entire stack of inspectors and correctly handling traces, logs, debugging info +/// collection, etc. #[derive(Clone, Debug, Default)] pub struct InspectorStack { pub cheatcodes: Option, + pub inner: InspectorStackInner, +} + +/// All used inpectors besides [Cheatcodes]. +/// +/// See [`InspectorStack`]. +#[derive(Default, Clone, Debug)] +pub struct InspectorStackInner { pub chisel_state: Option, pub coverage: Option, - pub debugger: Option, pub fuzzer: Option, pub log_collector: Option, pub printer: Option, @@ -286,6 +290,23 @@ pub struct InspectorStack { pub inner_context_data: Option, } +/// Struct keeping mutable references to both parts of [InspectorStack] and implementing +/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut] or via +/// [CheatcodesExecutor::get_inspector] method implemented for [InspectorStackInner]. +pub struct InspectorStackRefMut<'a> { + pub cheatcodes: Option<&'a mut Cheatcodes>, + pub inner: &'a mut InspectorStackInner, +} + +impl CheatcodesExecutor for InspectorStackInner { + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a { + InspectorStackRefMut { cheatcodes: Some(cheats), inner: self } + } +} + impl InspectorStack { /// Creates a new inspector stack. /// @@ -310,7 +331,7 @@ impl InspectorStack { )* }; } - push!(cheatcodes, chisel_state, coverage, debugger, fuzzer, log_collector, printer, tracer); + push!(cheatcodes, chisel_state, coverage, fuzzer, log_collector, printer, tracer); if self.enable_isolation { enabled.push("isolation"); } @@ -365,12 +386,6 @@ impl InspectorStack { self.coverage = yes.then(Default::default); } - /// Set whether to enable the debugger. - #[inline] - pub fn enable_debugger(&mut self, yes: bool) { - self.debugger = yes.then(Default::default); - } - /// Set whether to enable call isolation. #[inline] pub fn enable_isolation(&mut self, yes: bool) { @@ -391,52 +406,70 @@ impl InspectorStack { /// Set whether to enable the tracer. #[inline] - pub fn tracing(&mut self, yes: bool) { - self.tracer = yes.then(|| { - TracingInspector::new(TracingInspectorConfig { - record_steps: false, - record_memory_snapshots: false, - record_stack_snapshots: StackSnapshotType::None, - record_state_diff: false, - exclude_precompile_calls: false, - record_logs: true, - }) - }); + pub fn tracing(&mut self, mode: TraceMode) { + if let Some(config) = mode.into_config() { + *self.tracer.get_or_insert_with(Default::default).config_mut() = config; + } else { + self.tracer = None; + } } /// Collects all the data gathered during inspection into a single struct. #[inline] pub fn collect(self) -> InspectorData { + let Self { + cheatcodes, + inner: InspectorStackInner { chisel_state, coverage, log_collector, tracer, .. }, + } = self; + InspectorData { - logs: self.log_collector.map(|logs| logs.logs).unwrap_or_default(), - labels: self - .cheatcodes + logs: log_collector.map(|logs| logs.logs).unwrap_or_default(), + labels: cheatcodes .as_ref() .map(|cheatcodes| cheatcodes.labels.clone()) .unwrap_or_default(), - traces: self.tracer.map(|tracer| tracer.get_traces().clone()), - debug: self.debugger.map(|debugger| debugger.arena), - coverage: self.coverage.map(|coverage| coverage.maps), - cheatcodes: self.cheatcodes, - chisel_state: self.chisel_state.and_then(|state| state.state), + traces: tracer.map(|tracer| tracer.into_traces()), + coverage: coverage.map(|coverage| coverage.maps), + cheatcodes, + chisel_state: chisel_state.and_then(|state| state.state), } } - fn do_call_end( + #[inline(always)] + fn as_mut(&mut self) -> InspectorStackRefMut<'_> { + InspectorStackRefMut { cheatcodes: self.cheatcodes.as_mut(), inner: &mut self.inner } + } +} + +impl<'a> InspectorStackRefMut<'a> { + /// Adjusts the EVM data for the inner EVM context. + /// Should be called on the top-level call of inner context (depth == 0 && + /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility + /// Updates tx.origin to the value before entering inner context + fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EvmContext) { + let inner_context_data = + self.inner_context_data.as_ref().expect("should be called in inner context"); + let sender_acc = ecx + .journaled_state + .state + .get_mut(&inner_context_data.sender) + .expect("failed to load sender"); + if !inner_context_data.is_create { + sender_acc.info.nonce = inner_context_data.original_sender_nonce; + } + ecx.env.tx.caller = inner_context_data.original_origin; + } + + fn do_call_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { let result = outcome.result.result; call_inspectors_adjust_depth!( - [ - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.cheatcodes, - &mut self.printer, - ], + #[ret] + [&mut self.fuzzer, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { let new_outcome = inspector.call_end(ecx, inputs, outcome.clone()); @@ -454,9 +487,9 @@ impl InspectorStack { outcome } - fn transact_inner( + fn transact_inner( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, transact_to: TransactTo, caller: Address, input: Bytes, @@ -479,7 +512,7 @@ impl InspectorStack { ecx.env.block.basefee = U256::ZERO; ecx.env.tx.caller = caller; - ecx.env.tx.transact_to = transact_to.clone(); + ecx.env.tx.transact_to = transact_to; ecx.env.tx.data = input; ecx.env.tx.value = value; ecx.env.tx.nonce = Some(nonce); @@ -497,13 +530,17 @@ impl InspectorStack { sender: ecx.env.tx.caller, original_origin: cached_env.tx.caller, original_sender_nonce: nonce, - is_create: matches!(transact_to, TransactTo::Create), + is_create: matches!(transact_to, TxKind::Create), }); self.in_inner_context = true; let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); let res = { - let mut evm = crate::utils::new_evm_with_inspector(&mut *ecx.db, env, &mut *self); + let mut evm = crate::utils::new_evm_with_inspector( + &mut ecx.db as &mut dyn DatabaseExt, + env, + &mut *self, + ); let res = evm.transact(); // need to reset the env in case it was modified via cheatcodes during execution @@ -523,7 +560,7 @@ impl InspectorStack { // Should we match, encode and propagate error as a revert reason? let result = InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas }; - return (result, None) + return (result, None); }; // Commit changes after transaction @@ -536,7 +573,7 @@ impl InspectorStack { output: Bytes::from(e.to_string()), gas, }; - return (res, None) + return (res, None); } if let Err(e) = update_state(&mut res.state, &mut ecx.db, None) { let res = InterpreterResult { @@ -544,7 +581,7 @@ impl InspectorStack { output: Bytes::from(e.to_string()), gas, }; - return (res, None) + return (res, None); } // Merge transaction journal into the active journal. @@ -580,38 +617,11 @@ impl InspectorStack { }; (InterpreterResult { result, output, gas }, address) } - - /// Adjusts the EVM data for the inner EVM context. - /// Should be called on the top-level call of inner context (depth == 0 && - /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility - /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context( - &mut self, - ecx: &mut EvmContext<&mut DB>, - ) { - let inner_context_data = - self.inner_context_data.as_ref().expect("should be called in inner context"); - let sender_acc = ecx - .journaled_state - .state - .get_mut(&inner_context_data.sender) - .expect("failed to load sender"); - if !inner_context_data.is_create { - sender_acc.info.nonce = inner_context_data.original_sender_nonce; - } - ecx.env.tx.caller = inner_context_data.original_origin; - } } -// NOTE: `&mut DB` is required because we recurse inside of `transact_inner` and we need to use the -// same reference to the DB, otherwise there's infinite recursion and Rust fails to instatiate this -// implementation. This currently works because internally we only use `&mut DB` anyways, but if -// this ever needs to be changed, this can be reverted back to using just `DB`, and instead using -// dynamic dispatch (`&mut dyn ...`) in `transact_inner`. -impl Inspector<&mut DB> for InspectorStack { - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { +impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( - #[no_ret] [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.initialize_interp(interpreter, ecx), self, @@ -619,12 +629,10 @@ impl Inspector<&mut DB> for InspectorSt ); } - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( - #[no_ret] [ &mut self.fuzzer, - &mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes, @@ -636,45 +644,33 @@ impl Inspector<&mut DB> for InspectorSt ); } - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( - #[no_ret] - [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], + [&mut self.tracer, &mut self.chisel_state, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.step_end(interpreter, ecx), self, ecx ); } - fn log(&mut self, ecx: &mut EvmContext<&mut DB>, log: &Log) { + fn log(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext, log: &Log) { call_inspectors_adjust_depth!( - #[no_ret] [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - |inspector| inspector.log(ecx, log), + |inspector| inspector.log(interpreter, ecx, log), self, ecx ); } - fn call( - &mut self, - ecx: &mut EvmContext<&mut DB>, - call: &mut CallInputs, - ) -> Option { + fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 0 { self.adjust_evm_data_for_inner_context(ecx); return None; } call_inspectors_adjust_depth!( - [ - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - ], + #[ret] + [&mut self.fuzzer, &mut self.tracer, &mut self.log_collector, &mut self.printer], |inspector| { let mut out = None; if let Some(output) = inspector.call(ecx, call) { @@ -688,6 +684,17 @@ impl Inspector<&mut DB> for InspectorSt ecx ); + ecx.journaled_state.depth += self.in_inner_context as usize; + if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { + if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) { + if output.result.result != InstructionResult::Continue { + ecx.journaled_state.depth -= self.in_inner_context as usize; + return Some(output); + } + } + } + ecx.journaled_state.depth -= self.in_inner_context as usize; + if self.enable_isolation && call.scheme == CallScheme::Call && !self.in_inner_context && @@ -695,13 +702,13 @@ impl Inspector<&mut DB> for InspectorSt { let (result, _) = self.transact_inner( ecx, - TransactTo::Call(call.target_address), + TxKind::Call(call.target_address), call.caller, call.input.clone(), call.gas_limit, call.value.get(), ); - return Some(CallOutcome { result, memory_offset: call.return_memory_offset.clone() }) + return Some(CallOutcome { result, memory_offset: call.return_memory_offset.clone() }); } None @@ -709,14 +716,14 @@ impl Inspector<&mut DB> for InspectorSt fn call_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. // Avoid processing twice. if self.in_inner_context && ecx.journaled_state.depth == 0 { - return outcome + return outcome; } let outcome = self.do_call_end(ecx, inputs, outcome); @@ -734,7 +741,7 @@ impl Inspector<&mut DB> for InspectorSt fn create( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, create: &mut CreateInputs, ) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 0 { @@ -743,22 +750,27 @@ impl Inspector<&mut DB> for InspectorSt } call_inspectors_adjust_depth!( - [&mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + #[ret] + [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], |inspector| inspector.create(ecx, create).map(Some), self, ecx ); - if self.enable_isolation && !self.in_inner_context && ecx.journaled_state.depth == 1 { + if !matches!(create.scheme, CreateScheme::Create2 { .. }) && + self.enable_isolation && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { let (result, address) = self.transact_inner( ecx, - TransactTo::Create, + TxKind::Create, create.caller, create.init_code.clone(), create.gas_limit, create.value, ); - return Some(CreateOutcome { result, address }) + return Some(CreateOutcome { result, address }); } None @@ -766,20 +778,21 @@ impl Inspector<&mut DB> for InspectorSt fn create_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, call: &CreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. // Avoid processing twice. if self.in_inner_context && ecx.journaled_state.depth == 0 { - return outcome + return outcome; } let result = outcome.result.result; call_inspectors_adjust_depth!( - [&mut self.debugger, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + #[ret] + [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { let new_outcome = inspector.create_end(ecx, call, outcome.clone()); @@ -797,6 +810,82 @@ impl Inspector<&mut DB> for InspectorSt outcome } + fn eofcreate( + &mut self, + ecx: &mut EvmContext, + create: &mut EOFCreateInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } + + call_inspectors_adjust_depth!( + #[ret] + [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + |inspector| inspector.eofcreate(ecx, create).map(Some), + self, + ecx + ); + + if matches!(create.kind, EOFCreateKind::Tx { .. }) && + self.enable_isolation && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { + let init_code = match &mut create.kind { + EOFCreateKind::Tx { initdata } => initdata.clone(), + EOFCreateKind::Opcode { .. } => unreachable!(), + }; + + let (result, address) = self.transact_inner( + ecx, + TxKind::Create, + create.caller, + init_code, + create.gas_limit, + create.value, + ); + return Some(CreateOutcome { result, address }); + } + + None + } + + fn eofcreate_end( + &mut self, + ecx: &mut EvmContext, + call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. + // Avoid processing twice. + if self.in_inner_context && ecx.journaled_state.depth == 0 { + return outcome; + } + + let result = outcome.result.result; + + call_inspectors_adjust_depth!( + #[ret] + [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| { + let new_outcome = inspector.eofcreate_end(ecx, call, outcome.clone()); + + // If the inspector returns a different status or a revert with a non-empty message, + // we assume it wants to tell us something + let different = new_outcome.result.result != result || + (new_outcome.result.result == InstructionResult::Revert && + new_outcome.output() != outcome.output()); + different.then_some(new_outcome) + }, + self, + ecx + ); + + outcome + } + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { Inspector::::selfdestruct(inspector, contract, target, value) @@ -804,13 +893,14 @@ impl Inspector<&mut DB> for InspectorSt } } -impl InspectorExt<&mut DB> for InspectorStack { +impl<'a, DB: DatabaseExt> InspectorExt for InspectorStackRefMut<'a> { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &mut CreateInputs, ) -> bool { call_inspectors_adjust_depth!( + #[ret] [&mut self.cheatcodes], |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, self, @@ -819,4 +909,123 @@ impl InspectorExt<&mut DB> for Inspecto false } + + fn console_log(&mut self, input: String) { + call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::::console_log( + inspector, input + )); + } +} + +impl Inspector for InspectorStack { + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().step(interpreter, ecx) + } + + #[inline] + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().step_end(interpreter, ecx) + } + + fn call( + &mut self, + context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + self.as_mut().call(context, inputs) + } + + fn call_end( + &mut self, + context: &mut EvmContext, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + self.as_mut().call_end(context, inputs, outcome) + } + + fn create( + &mut self, + context: &mut EvmContext, + create: &mut CreateInputs, + ) -> Option { + self.as_mut().create(context, create) + } + + fn create_end( + &mut self, + context: &mut EvmContext, + call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.as_mut().create_end(context, call, outcome) + } + + fn eofcreate( + &mut self, + context: &mut EvmContext, + create: &mut EOFCreateInputs, + ) -> Option { + self.as_mut().eofcreate(context, create) + } + + fn eofcreate_end( + &mut self, + context: &mut EvmContext, + call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.as_mut().eofcreate_end(context, call, outcome) + } + + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().initialize_interp(interpreter, ecx) + } + + fn log(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext, log: &Log) { + self.as_mut().log(interpreter, ecx, log) + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + Inspector::::selfdestruct(&mut self.as_mut(), contract, target, value) + } +} + +impl InspectorExt for InspectorStack { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> bool { + self.as_mut().should_use_create2_factory(ecx, inputs) + } +} + +impl<'a> Deref for InspectorStackRefMut<'a> { + type Target = &'a mut InspectorStackInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for InspectorStackRefMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Deref for InspectorStack { + type Target = InspectorStackInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for InspectorStack { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 598012770..8bbd7f141 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -11,7 +11,7 @@ extern crate tracing; pub mod executors; pub mod inspectors; -pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils, InspectorExt}; +pub use foundry_evm_core::{backend, constants, decode, fork, opts, utils, InspectorExt}; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; pub use foundry_evm_traces as traces; diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index c6449d312..c17376bfb 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -43,7 +43,7 @@ revm = { workspace = true, features = [ eyre .workspace = true itertools.workspace = true parking_lot.workspace = true -proptest = "1" +proptest.workspace = true rand.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index f675e7755..8f24cba30 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -10,7 +10,7 @@ extern crate tracing; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_primitives::{Address, Bytes, Log}; -use foundry_common::{calc, contracts::ContractsByAddress}; +use foundry_common::{calc, contracts::ContractsByAddress, evm::Breakpoints}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use itertools::Itertools; @@ -163,9 +163,6 @@ pub struct FuzzTestResult { /// be printed to the user. pub logs: Vec, - /// The decoded DSTest logging events and Hardhat's `console.log` from [logs](Self::logs). - pub decoded_logs: Vec, - /// Labeled addresses pub labeled_addresses: HashMap, @@ -181,6 +178,9 @@ pub struct FuzzTestResult { /// Raw coverage info pub coverage: Option, + + /// Breakpoints for debugger. Correspond to the same fuzz case as `traces`. + pub breakpoints: Option, } impl FuzzTestResult { diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 9f1c15c13..53c3d1042 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -18,6 +18,7 @@ foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-cheatcodes-spec.workspace = true foundry-compilers.workspace = true +foundry-linking.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true @@ -32,17 +33,18 @@ alloy-primitives = { workspace = true, features = [ alloy-sol-types.workspace = true revm-inspectors.workspace = true -eyre .workspace = true +eyre.workspace = true futures.workspace = true -hex.workspace = true itertools.workspace = true once_cell.workspace = true serde.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true -yansi.workspace = true rustc-hash.workspace = true tempfile.workspace = true +rayon.workspace = true +solang-parser.workspace = true +revm.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/evm/traces/src/debug/mod.rs b/crates/evm/traces/src/debug/mod.rs new file mode 100644 index 000000000..a651a3acc --- /dev/null +++ b/crates/evm/traces/src/debug/mod.rs @@ -0,0 +1,324 @@ +mod sources; +use crate::CallTraceNode; +use alloy_dyn_abi::{ + parser::{Parameters, Storage}, + DynSolType, DynSolValue, Specifier, +}; +use alloy_primitives::U256; +use foundry_common::fmt::format_token; +use foundry_compilers::artifacts::sourcemap::{Jump, SourceElement}; +use revm::interpreter::OpCode; +use revm_inspectors::tracing::types::{CallTraceStep, DecodedInternalCall, DecodedTraceStep}; +pub use sources::{ArtifactData, ContractSources, SourceData}; + +#[derive(Clone, Debug)] +pub struct DebugTraceIdentifier { + /// Source map of contract sources + contracts_sources: ContractSources, +} + +impl DebugTraceIdentifier { + pub fn new(contracts_sources: ContractSources) -> Self { + Self { contracts_sources } + } + + /// Identifies internal function invocations in a given [CallTraceNode]. + /// + /// Accepts the node itself and identified name of the contract which node corresponds to. + pub fn identify_node_steps(&self, node: &mut CallTraceNode, contract_name: &str) { + DebugStepsWalker::new(node, &self.contracts_sources, contract_name).walk(); + } +} + +/// Walks through the [CallTraceStep]s attempting to match JUMPs to internal functions. +/// +/// This is done by looking up jump kinds in the source maps. The structure of internal function +/// call always looks like this: +/// - JUMP +/// - JUMPDEST +/// ... function steps ... +/// - JUMP +/// - JUMPDEST +/// +/// The assumption we rely on is that first JUMP into function will be marked as [Jump::In] in +/// source map, and second JUMP out of the function will be marked as [Jump::Out]. +/// +/// Also, we rely on JUMPDEST after first JUMP pointing to the source location of the body of +/// function which was entered. We pass this source part to [parse_function_from_loc] to extract the +/// function name. +/// +/// When we find a [Jump::In] and identify the function name, we push it to the stack. +/// +/// When we find a [Jump::Out] we try to find a matching [Jump::In] in the stack. A match is found +/// when source location of the JUMP-in matches the source location of final JUMPDEST (this would be +/// the location of the function invocation), or when source location of first JUMODEST matches the +/// source location of the JUMP-out (this would be the location of function body). +/// +/// When a match is found, all items which were pushed after the matched function are removed. There +/// is a lot of such items due to source maps getting malformed during optimization. +struct DebugStepsWalker<'a> { + node: &'a mut CallTraceNode, + current_step: usize, + stack: Vec<(String, usize)>, + sources: &'a ContractSources, + contract_name: &'a str, +} + +impl<'a> DebugStepsWalker<'a> { + pub fn new( + node: &'a mut CallTraceNode, + sources: &'a ContractSources, + contract_name: &'a str, + ) -> Self { + Self { node, current_step: 0, stack: Vec::new(), sources, contract_name } + } + + fn current_step(&self) -> &CallTraceStep { + &self.node.trace.steps[self.current_step] + } + + 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.kind.is_any_create(), + ) + } + + fn prev_src_map(&self) -> Option<(SourceElement, &SourceData)> { + if self.current_step == 0 { + return None; + } + + self.src_map(self.current_step - 1) + } + + fn current_src_map(&self) -> Option<(SourceElement, &SourceData)> { + self.src_map(self.current_step) + } + + fn is_same_loc(&self, step: usize, other: usize) -> bool { + let Some((loc, _)) = self.src_map(step) else { + return false; + }; + let Some((other_loc, _)) = self.src_map(other) else { + return false; + }; + + loc.offset() == other_loc.offset() && + loc.length() == other_loc.length() && + loc.index() == other_loc.index() + } + + /// Invoked when current step is a JUMPDEST preceeded by a JUMP marked as [Jump::In]. + fn jump_in(&mut self) { + // This usually means that this is a jump into the external function which is an + // entrypoint for the current frame. We don't want to include this to avoid + // duplicating traces. + if self.is_same_loc(self.current_step, self.current_step - 1) { + return; + } + + let Some((source_element, source)) = self.current_src_map() else { + return; + }; + + if let Some(name) = parse_function_from_loc(source, &source_element) { + self.stack.push((name, self.current_step - 1)); + } + } + + /// Invoked when current step is a JUMPDEST preceeded by a JUMP marked as [Jump::Out]. + fn jump_out(&mut self) { + let Some((i, _)) = self.stack.iter().enumerate().rfind(|(_, (_, step_idx))| { + self.is_same_loc(*step_idx, self.current_step) || + self.is_same_loc(step_idx + 1, self.current_step - 1) + }) else { + return + }; + // We've found a match, remove all records between start and end, those + // are considered invalid. + let (func_name, start_idx) = self.stack.split_off(i).swap_remove(0); + + // Try to decode function inputs and outputs from the stack and memory. + let (inputs, outputs) = self + .src_map(start_idx + 1) + .map(|(source_element, source)| { + let start = source_element.offset() as usize; + let end = start + source_element.length() as usize; + let fn_definition = source.source[start..end].replace('\n', ""); + let (inputs, outputs) = parse_types(&fn_definition); + + ( + inputs.and_then(|t| { + try_decode_args_from_step(&t, &self.node.trace.steps[start_idx + 1]) + }), + outputs.and_then(|t| try_decode_args_from_step(&t, self.current_step())), + ) + }) + .unwrap_or_default(); + + self.node.trace.steps[start_idx].decoded = Some(DecodedTraceStep::InternalCall( + DecodedInternalCall { func_name, args: inputs, return_data: outputs }, + self.current_step, + )); + } + + fn process(&mut self) { + // We are only interested in JUMPs. + if self.current_step().op != OpCode::JUMP && self.current_step().op != OpCode::JUMPDEST { + return; + } + + let Some((prev_source_element, _)) = self.prev_src_map() else { + return; + }; + + match prev_source_element.jump() { + Jump::In => self.jump_in(), + Jump::Out => self.jump_out(), + _ => {} + }; + } + + fn step(&mut self) { + self.process(); + self.current_step += 1; + } + + pub fn walk(mut self) { + while self.current_step < self.node.trace.steps.len() { + self.step(); + } + } +} + +/// Tries to parse the function name from the source code and detect the contract name which +/// contains the given function. +/// +/// Returns string in the format `Contract::function`. +fn parse_function_from_loc(source: &SourceData, loc: &SourceElement) -> Option { + let start = loc.offset() as usize; + let end = start + loc.length() as usize; + let source_part = &source.source[start..end]; + if !source_part.starts_with("function") { + return None; + } + let function_name = source_part.split_once("function")?.1.split('(').next()?.trim(); + let contract_name = source.find_contract_name(start, end)?; + + Some(format!("{contract_name}::{function_name}")) +} + +/// Parses function input and output types into [Parameters]. +fn parse_types(source: &str) -> (Option>, Option>) { + let inputs = source.find('(').and_then(|params_start| { + let params_end = params_start + source[params_start..].find(')')?; + Parameters::parse(&source[params_start..params_end + 1]).ok() + }); + let outputs = source.find("returns").and_then(|returns_start| { + let return_params_start = returns_start + source[returns_start..].find('(')?; + let return_params_end = return_params_start + source[return_params_start..].find(')')?; + Parameters::parse(&source[return_params_start..return_params_end + 1]).ok() + }); + + (inputs, outputs) +} + +/// Given [Parameters] and [CallTraceStep], tries to decode parameters by using stack and memory. +fn try_decode_args_from_step(args: &Parameters<'_>, step: &CallTraceStep) -> Option> { + let params = &args.params; + + if params.is_empty() { + return Some(vec![]); + } + + let types = params.iter().map(|p| p.resolve().ok().map(|t| (t, p.storage))).collect::>(); + + let stack = step.stack.as_ref()?; + + if stack.len() < types.len() { + return None; + } + + let inputs = &stack[stack.len() - types.len()..]; + + let decoded = inputs + .iter() + .zip(types.iter()) + .map(|(input, type_and_storage)| { + type_and_storage + .as_ref() + .and_then(|(type_, storage)| { + match (type_, storage) { + // HACK: alloy parser treats user-defined types as uint8: https://github.com/alloy-rs/core/pull/386 + // + // filter out `uint8` params which are marked as storage or memory as this + // is not possible in Solidity and means that type is user-defined + (DynSolType::Uint(8), Some(Storage::Memory | Storage::Storage)) => None, + (_, Some(Storage::Memory)) => decode_from_memory( + type_, + step.memory.as_ref()?.as_bytes(), + input.try_into().ok()?, + ), + // Read other types from stack + _ => type_.abi_decode(&input.to_be_bytes::<32>()).ok(), + } + }) + .as_ref() + .map(format_token) + .unwrap_or_else(|| "".to_string()) + }) + .collect(); + + Some(decoded) +} + +/// Decodes given [DynSolType] from memory. +fn decode_from_memory(ty: &DynSolType, memory: &[u8], location: usize) -> Option { + let first_word = memory.get(location..location + 32)?; + + match ty { + // For `string` and `bytes` layout is a word with length followed by the data + DynSolType::String | DynSolType::Bytes => { + let length: usize = U256::from_be_slice(first_word).try_into().ok()?; + let data = memory.get(location + 32..location + 32 + length)?; + + match ty { + DynSolType::Bytes => Some(DynSolValue::Bytes(data.to_vec())), + DynSolType::String => { + Some(DynSolValue::String(String::from_utf8_lossy(data).to_string())) + } + _ => unreachable!(), + } + } + // Dynamic arrays are encoded as a word with length followed by words with elements + // Fixed arrays are encoded as words with elements + DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => { + let (length, start) = match ty { + DynSolType::FixedArray(_, length) => (*length, location), + DynSolType::Array(_) => { + (U256::from_be_slice(first_word).try_into().ok()?, location + 32) + } + _ => unreachable!(), + }; + let mut decoded = Vec::with_capacity(length); + + for i in 0..length { + let offset = start + i * 32; + let location = match inner.as_ref() { + // Arrays of variable length types are arrays of pointers to the values + DynSolType::String | DynSolType::Bytes | DynSolType::Array(_) => { + U256::from_be_slice(memory.get(offset..offset + 32)?).try_into().ok()? + } + _ => offset, + }; + + decoded.push(decode_from_memory(inner, memory, location)?); + } + + Some(DynSolValue::Array(decoded)) + } + _ => ty.abi_decode(first_word).ok(), + } +} diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs new file mode 100644 index 000000000..feb870381 --- /dev/null +++ b/crates/evm/traces/src/debug/sources.rs @@ -0,0 +1,288 @@ +use eyre::{Context, Result}; +use foundry_common::compact_to_contract; +use foundry_compilers::{ + artifacts::{ + sourcemap::{SourceElement, SourceMap}, + Bytecode, ContractBytecodeSome, Libraries, Source, + }, + multi::MultiCompilerLanguage, + Artifact, Compiler, ProjectCompileOutput, +}; +use foundry_evm_core::utils::PcIcMap; +use foundry_linking::Linker; +use rayon::prelude::*; +use rustc_hash::FxHashMap; +use solang_parser::pt::SourceUnitPart; +use std::{ + collections::{BTreeMap, HashMap}, + path::{Path, PathBuf}, + sync::Arc, +}; + +#[derive(Clone, Debug)] +pub struct SourceData { + pub source: Arc, + pub language: MultiCompilerLanguage, + pub path: PathBuf, + /// Maps contract name to (start, end) of the contract definition in the source code. + /// This is useful for determining which contract contains given function definition. + contract_definitions: Vec<(String, usize, usize)>, +} + +impl SourceData { + pub fn new(source: Arc, language: MultiCompilerLanguage, path: PathBuf) -> Self { + let mut contract_definitions = Vec::new(); + + match language { + MultiCompilerLanguage::Vyper(_) => { + // Vyper contracts have the same name as the file name. + if let Some(name) = path.file_name().map(|s| s.to_string_lossy().to_string()) { + contract_definitions.push((name, 0, source.len())); + } + } + MultiCompilerLanguage::Solc(_) => { + if let Ok((parsed, _)) = solang_parser::parse(&source, 0) { + for item in parsed.0 { + let SourceUnitPart::ContractDefinition(contract) = item else { + continue; + }; + let Some(name) = contract.name else { + continue; + }; + contract_definitions.push(( + name.name, + name.loc.start(), + contract.loc.end(), + )); + } + } + } + } + + Self { source, language, path, contract_definitions } + } + + /// Finds name of contract that contains given loc. + pub fn find_contract_name(&self, start: usize, end: usize) -> Option<&str> { + self.contract_definitions + .iter() + .find(|(_, s, e)| start >= *s && end <= *e) + .map(|(name, _, _)| name.as_str()) + } +} + +#[derive(Clone, Debug)] +pub struct ArtifactData { + 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 ArtifactData { + fn new(bytecode: ContractBytecodeSome, build_id: String, file_id: u32) -> Result { + let parse = |b: &Bytecode| { + // Only parse source map if it's not empty. + let source_map = if b.source_map.as_ref().map_or(true, |s| s.is_empty()) { + Ok(None) + } else { + b.source_map().transpose() + }; + + // Only parse bytecode if it's not empty. + let pc_ic_map = if let Some(bytes) = b.bytes() { + (!bytes.is_empty()).then(|| PcIcMap::new(bytes)) + } else { + None + }; + + source_map.map(|source_map| (source_map, pc_ic_map)) + }; + let (source_map, pc_ic_map) = parse(&bytecode.bytecode)?; + let (source_map_runtime, pc_ic_map_runtime) = bytecode + .deployed_bytecode + .bytecode + .map(|b| parse(&b)) + .unwrap_or_else(|| Ok((None, None)))?; + + Ok(Self { source_map, source_map_runtime, pc_ic_map, pc_ic_map_runtime, build_id, file_id }) + } +} + +/// Container with artifacts data useful for identifying individual execution steps. +#[derive(Clone, Debug, Default)] +pub struct ContractSources { + /// Map over build_id -> file_id -> (source code, language) + pub sources_by_id: HashMap>>, + /// Map over contract name -> Vec<(bytecode, build_id, file_id)> + pub artifacts_by_name: HashMap>, +} + +impl ContractSources { + /// Collects the contract sources and artifacts from the project compile output. + pub fn from_project_output( + output: &ProjectCompileOutput, + root: impl AsRef, + libraries: Option<&Libraries>, + ) -> Result { + let mut sources = Self::default(); + sources.insert(output, root, libraries)?; + Ok(sources) + } + + pub fn insert( + &mut self, + output: &ProjectCompileOutput, + root: impl AsRef, + libraries: Option<&Libraries>, + ) -> Result<()> + where + C::Language: Into, + { + let root = root.as_ref(); + let link_data = libraries.map(|libraries| { + let linker = Linker::new(root, output.artifact_ids().collect()); + (linker, libraries) + }); + + let artifacts: Vec<_> = output + .artifact_ids() + .collect::>() + .par_iter() + .map(|(id, artifact)| { + let mut artifacts = Vec::new(); + if let Some(file_id) = artifact.id { + let artifact = if let Some((linker, libraries)) = link_data.as_ref() { + linker.link(id, libraries)?.into_contract_bytecode() + } else { + (*artifact).clone().into_contract_bytecode() + }; + let bytecode = compact_to_contract(artifact.into_contract_bytecode())?; + + artifacts.push(( + id.name.clone(), + ArtifactData::new(bytecode, id.build_id.clone(), file_id)?, + )); + } else { + warn!(id = id.identifier(), "source not found"); + }; + + Ok(artifacts) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect(); + + for (name, artifact) in artifacts { + self.artifacts_by_name.entry(name).or_default().push(artifact); + } + + // Not all source files produce artifacts, so we are populating sources by using build + // infos. + let mut files: BTreeMap> = BTreeMap::new(); + for (build_id, build) in output.builds() { + for (source_id, path) in &build.source_id_to_path { + let source_data = if let Some(source_data) = files.get(path) { + source_data.clone() + } else { + let source = Source::read(path).wrap_err_with(|| { + format!("failed to read artifact source file for `{}`", path.display()) + })?; + + let stripped = path.strip_prefix(root).unwrap_or(path).to_path_buf(); + + let source_data = Arc::new(SourceData::new( + source.content.clone(), + build.language.into(), + stripped, + )); + + files.insert(path.clone(), source_data.clone()); + + source_data + }; + + self.sources_by_id + .entry(build_id.clone()) + .or_default() + .insert(*source_id, source_data); + } + } + + Ok(()) + } + + /// Returns all sources for a contract by name. + pub fn get_sources( + &self, + name: &str, + ) -> Option> { + self.artifacts_by_name.get(name).map(|artifacts| { + artifacts.iter().filter_map(|artifact| { + let source = + self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?; + Some((artifact, source.as_ref())) + }) + }) + } + + /// Returns all (name, bytecode, source) sets. + pub fn entries(&self) -> impl Iterator { + self.artifacts_by_name.iter().flat_map(|(name, artifacts)| { + artifacts.iter().filter_map(|artifact| { + let source = + self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?; + Some((name.as_str(), artifact, source.as_ref())) + }) + }) + } + + pub fn find_source_mapping( + &self, + contract_name: &str, + pc: usize, + init_code: bool, + ) -> Option<(SourceElement, &SourceData)> { + self.get_sources(contract_name)?.find_map(|(artifact, source)| { + let source_map = if init_code { + artifact.source_map.as_ref() + } else { + artifact.source_map_runtime.as_ref() + }?; + + // Solc indexes source maps by instruction counter, but Vyper indexes by program + // counter. + let source_element = if matches!(source.language, MultiCompilerLanguage::Solc(_)) { + let pc_ic_map = if init_code { + artifact.pc_ic_map.as_ref() + } else { + artifact.pc_ic_map_runtime.as_ref() + }?; + let ic = pc_ic_map.get(pc)?; + + source_map.get(ic)? + } else { + source_map.get(pc)? + }; + // if the source element has an index, find the sourcemap for that index + let res = source_element + .index() + // if index matches current file_id, return current source code + .and_then(|index| { + (index == artifact.file_id).then(|| (source_element.clone(), source)) + }) + .or_else(|| { + // otherwise find the source code for the element's index + self.sources_by_id + .get(&artifact.build_id)? + .get(&source_element.index()?) + .map(|source| (source_element.clone(), source.as_ref())) + }); + + res + }) + } +} diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 1985fc2cd..f41dbd7a3 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -1,26 +1,29 @@ use crate::{ + debug::DebugTraceIdentifier, identifier::{ AddressIdentity, LocalTraceIdentifier, SingleSignaturesIdentifier, TraceIdentifier, }, - CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, DecodedCallLog, DecodedCallTrace, + CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, }; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Error, Event, Function, JsonAbi}; use alloy_primitives::{Address, LogData, Selector, B256}; use foundry_cheatcodes_spec::Vm; use foundry_common::{ - abi::get_indexed_event, fmt::format_token, Console, ContractsByArtifact, HardhatConsole, - HARDHAT_CONSOLE_SELECTOR_PATCHES, SELECTOR_LEN, + abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN, }; use foundry_evm_core::{ - constants::{ - CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, - TEST_CONTRACT_ADDRESS, - }, + abi::{Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS, HARDHAT_CONSOLE_SELECTOR_PATCHES}, + constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, decode::RevertDecoder, + precompiles::{ + BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, + RIPEMD_160, SHA_256, + }, }; use itertools::Itertools; use once_cell::sync::OnceCell; +use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; use rustc_hash::FxHashMap; use std::collections::{hash_map::Entry, BTreeMap, HashMap}; @@ -84,6 +87,13 @@ impl CallTraceDecoderBuilder { self } + /// Sets the debug identifier for the decoder. + #[inline] + pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self { + self.decoder.debug_identifier = Some(identifier); + self + } + /// Build the decoder. #[inline] pub fn build(self) -> CallTraceDecoder { @@ -120,6 +130,9 @@ pub struct CallTraceDecoder { pub signature_identifier: Option, /// Verbosity level pub verbosity: u8, + + /// Optional identifier of individual trace steps. + pub debug_identifier: Option, } impl CallTraceDecoder { @@ -160,6 +173,16 @@ impl CallTraceDecoder { (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()), (CALLER, "DefaultSender".to_string()), (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()), + (EC_RECOVER, "ECRecover".to_string()), + (SHA_256, "SHA-256".to_string()), + (RIPEMD_160, "RIPEMD-160".to_string()), + (IDENTITY, "Identity".to_string()), + (MOD_EXP, "ModExp".to_string()), + (EC_ADD, "ECAdd".to_string()), + (EC_MUL, "ECMul".to_string()), + (EC_PAIRING, "ECPairing".to_string()), + (BLAKE_2F, "Blake2F".to_string()), + (POINT_EVALUATION, "PointEvaluation".to_string()), ] .into(), receive_contracts: Default::default(), @@ -182,6 +205,8 @@ impl CallTraceDecoder { signature_identifier: None, verbosity: 0, + + debug_identifier: None, } } @@ -291,31 +316,38 @@ impl CallTraceDecoder { } } + /// Populates the traces with decoded data by mutating the + /// [CallTrace] in place. See [CallTraceDecoder::decode_function] and + /// [CallTraceDecoder::decode_event] for more details. + pub async fn populate_traces(&self, traces: &mut Vec) { + for node in traces { + node.trace.decoded = self.decode_function(&node.trace).await; + for log in node.logs.iter_mut() { + log.decoded = self.decode_event(&log.raw_log).await; + } + + if let Some(debug) = self.debug_identifier.as_ref() { + if let Some(identified) = self.contracts.get(&node.trace.address) { + debug.identify_node_steps(node, get_contract_name(identified)) + } + } + } + } + + /// Decodes a call trace. pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace { - // Decode precompile - if let Some((label, func)) = precompiles::decode(trace, 1) { - return DecodedCallTrace { - label: Some(label), - return_data: None, - contract: None, - func: Some(func), - }; + if let Some(trace) = precompiles::decode(trace, 1) { + return trace; } - // Set label let label = self.labels.get(&trace.address).cloned(); - // Set contract name - let contract = self.contracts.get(&trace.address).cloned(); - let cdata = &trace.data; if trace.address == DEFAULT_CREATE2_DEPLOYER { return DecodedCallTrace { label, - return_data: (!trace.status.is_ok()) - .then(|| self.revert_decoder.decode(&trace.output, Some(trace.status))), - contract, - func: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }), + call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }), + return_data: self.default_return_data(trace), }; } @@ -336,14 +368,17 @@ impl CallTraceDecoder { } }; let [func, ..] = &functions[..] else { - return DecodedCallTrace { label, return_data: None, contract, func: None }; + return DecodedCallTrace { + label, + call_data: None, + return_data: self.default_return_data(trace), + }; }; DecodedCallTrace { label, - func: Some(self.decode_function_input(trace, func)), + call_data: Some(self.decode_function_input(trace, func)), return_data: self.decode_function_output(trace, functions), - contract, } } else { let has_receive = self.receive_contracts.contains(&trace.address); @@ -352,13 +387,8 @@ impl CallTraceDecoder { let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] }; DecodedCallTrace { label, - return_data: if !trace.success { - Some(self.revert_decoder.decode(&trace.output, Some(trace.status))) - } else { - None - }, - contract, - func: Some(DecodedCallData { signature, args }), + call_data: Some(DecodedCallData { signature, args }), + return_data: self.default_return_data(trace), } } } @@ -376,7 +406,7 @@ impl CallTraceDecoder { if args.is_none() { if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) { - args = Some(v.iter().map(|value| self.apply_label(value)).collect()); + args = Some(v.iter().map(|value| self.format_value(value)).collect()); } } } @@ -489,34 +519,32 @@ impl CallTraceDecoder { /// Decodes a function's output into the given trace. fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option { - let data = &trace.output; - if trace.success { - if trace.address == CHEATCODE_ADDRESS { - if let Some(decoded) = - funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) - { - return Some(decoded); - } - } + if !trace.success { + return self.default_return_data(trace); + } - if let Some(values) = - funcs.iter().find_map(|func| func.abi_decode_output(data, false).ok()) + if trace.address == CHEATCODE_ADDRESS { + if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) { - // Functions coming from an external database do not have any outputs specified, - // and will lead to returning an empty list of values. - if values.is_empty() { - return None; - } + return Some(decoded); + } + } - return Some( - values.iter().map(|value| self.apply_label(value)).format(", ").to_string(), - ); + if let Some(values) = + funcs.iter().find_map(|func| func.abi_decode_output(&trace.output, false).ok()) + { + // Functions coming from an external database do not have any outputs specified, + // and will lead to returning an empty list of values. + if values.is_empty() { + return None; } - None - } else { - Some(self.revert_decoder.decode(data, Some(trace.status))) + return Some( + values.iter().map(|value| self.format_value(value)).format(", ").to_string(), + ); } + + None } /// Custom decoding for cheatcode outputs. @@ -532,9 +560,14 @@ impl CallTraceDecoder { .map(Into::into) } + /// The default decoded return data for a trace. + fn default_return_data(&self, trace: &CallTrace) -> Option { + (!trace.success).then(|| self.revert_decoder.decode(&trace.output, Some(trace.status))) + } + /// Decodes an event. - pub async fn decode_event<'a>(&self, log: &'a LogData) -> DecodedCallLog<'a> { - let &[t0, ..] = log.topics() else { return DecodedCallLog::Raw(log) }; + pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog { + let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } }; let mut events = Vec::new(); let events = match self.events.get(&(t0, log.topics().len() - 1)) { @@ -551,22 +584,24 @@ impl CallTraceDecoder { for event in events { if let Ok(decoded) = event.decode_log(log, false) { let params = reconstruct_params(event, &decoded); - return DecodedCallLog::Decoded( - event.name.clone(), - params - .into_iter() - .zip(event.inputs.iter()) - .map(|(param, input)| { - // undo patched names - let name = input.name.clone(); - (name, self.apply_label(¶m)) - }) - .collect(), - ); + return DecodedCallLog { + name: Some(event.name.clone()), + params: Some( + params + .into_iter() + .zip(event.inputs.iter()) + .map(|(param, input)| { + // undo patched names + let name = input.name.clone(); + (name, self.format_value(¶m)) + }) + .collect(), + ), + }; } } - DecodedCallLog::Raw(log) + DecodedCallLog { name: None, params: None } } /// Prefetches function and event signatures into the identifier cache @@ -575,7 +610,7 @@ impl CallTraceDecoder { let events_it = nodes .iter() - .flat_map(|node| node.logs.iter().filter_map(|log| log.topics().first())) + .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.topics().first())) .unique(); identifier.write().await.identify_events(events_it).await; @@ -592,7 +627,8 @@ impl CallTraceDecoder { identifier.write().await.identify_functions(funcs_it).await; } - fn apply_label(&self, value: &DynSolValue) -> String { + /// Pretty-prints a value. + fn format_value(&self, value: &DynSolValue) -> String { if let DynSolValue::Address(addr) = value { if let Some(label) = self.labels.get(addr) { return format!("{label}: [{addr}]"); diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index 1475208f4..0719f29b7 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -1,7 +1,12 @@ use crate::{CallTrace, DecodedCallData}; -use alloy_primitives::{B256, U256}; +use alloy_primitives::{hex, B256, U256}; use alloy_sol_types::{abi, sol, SolCall}; +use foundry_evm_core::precompiles::{ + BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, + RIPEMD_160, SHA_256, +}; use itertools::Itertools; +use revm_inspectors::tracing::types::DecodedCallTrace; sol! { /// EVM precompiles interface. For illustration purposes only, as precompiles don't follow the @@ -42,39 +47,42 @@ macro_rules! tri { } /// Tries to decode a precompile call. Returns `Some` if successful. -pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option<(String, DecodedCallData)> { - let [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, x @ 0x01..=0x0a] = - trace.address.0 .0 - else { - return None - }; +pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option { + if !trace.address[..19].iter().all(|&x| x == 0) { + return None; + } let data = &trace.data; - let (signature, args) = match x { - 0x01 => { + let (signature, args) = match trace.address { + EC_RECOVER => { let (sig, ecrecoverCall { hash, v, r, s }) = tri!(abi_decode_call(data)); (sig, vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()]) } - 0x02 => (sha256Call::SIGNATURE, vec![data.to_string()]), - 0x03 => (ripemdCall::SIGNATURE, vec![data.to_string()]), - 0x04 => (identityCall::SIGNATURE, vec![data.to_string()]), - 0x05 => (modexpCall::SIGNATURE, tri!(decode_modexp(data))), - 0x06 => { + SHA_256 => (sha256Call::SIGNATURE, vec![data.to_string()]), + RIPEMD_160 => (ripemdCall::SIGNATURE, vec![data.to_string()]), + IDENTITY => (identityCall::SIGNATURE, vec![data.to_string()]), + MOD_EXP => (modexpCall::SIGNATURE, tri!(decode_modexp(data))), + EC_ADD => { let (sig, ecaddCall { x1, y1, x2, y2 }) = tri!(abi_decode_call(data)); (sig, vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()]) } - 0x07 => { + EC_MUL => { let (sig, ecmulCall { x1, y1, s }) = tri!(abi_decode_call(data)); (sig, vec![x1.to_string(), y1.to_string(), s.to_string()]) } - 0x08 => (ecpairingCall::SIGNATURE, tri!(decode_ecpairing(data))), - 0x09 => (blake2fCall::SIGNATURE, tri!(decode_blake2f(data))), - 0x0a => (pointEvaluationCall::SIGNATURE, tri!(decode_kzg(data))), - 0x00 | 0x0b.. => unreachable!(), + EC_PAIRING => (ecpairingCall::SIGNATURE, tri!(decode_ecpairing(data))), + BLAKE_2F => (blake2fCall::SIGNATURE, tri!(decode_blake2f(data))), + POINT_EVALUATION => (pointEvaluationCall::SIGNATURE, tri!(decode_kzg(data))), + _ => return None, }; - Some(("PRECOMPILES".into(), DecodedCallData { signature: signature.to_string(), args })) + Some(DecodedCallTrace { + label: Some("PRECOMPILES".to_string()), + call_data: Some(DecodedCallData { signature: signature.to_string(), args }), + // TODO: Decode return data too. + return_data: None, + }) } // Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a diff --git a/crates/evm/traces/src/identifier/etherscan.rs b/crates/evm/traces/src/identifier/etherscan.rs index 87d7e9c92..4a34b31b3 100644 --- a/crates/evm/traces/src/identifier/etherscan.rs +++ b/crates/evm/traces/src/identifier/etherscan.rs @@ -1,10 +1,11 @@ use super::{AddressIdentity, TraceIdentifier}; +use crate::debug::ContractSources; use alloy_primitives::Address; use foundry_block_explorers::{ contract::{ContractMetadata, Metadata}, errors::EtherscanError, }; -use foundry_common::compile::{etherscan_project, ContractSources}; +use foundry_common::compile::etherscan_project; use foundry_config::{Chain, Config}; use futures::{ future::{join_all, Future}, @@ -85,7 +86,7 @@ impl EtherscanIdentifier { // construct the map for res in outputs { - let (project, output, _) = res?; + let (project, output, _root) = res?; sources.insert(&output, project.root(), None)?; } diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index cd69cf947..2a2f6a2f2 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -1,4 +1,5 @@ use alloy_json_abi::{Event, Function}; +use alloy_primitives::hex; use foundry_common::{ abi::{get_event, get_func}, fs, @@ -21,19 +22,17 @@ struct CachedSignatures { } /// An identifier that tries to identify functions and events using signatures found at -/// `https://openchain.xyz`. +/// `https://openchain.xyz` or a local cache. #[derive(Debug)] pub struct SignaturesIdentifier { - /// Cached selectors for functions and events + /// Cached selectors for functions and events. cached: CachedSignatures, - /// Location where to save `CachedSignatures` + /// Location where to save `CachedSignatures`. cached_path: Option, /// Selectors that were unavailable during the session. unavailable: HashSet, - /// The API client to fetch signatures from - sign_eth_api: OpenChainClient, - /// whether traces should be decoded via `sign_eth_api` - offline: bool, + /// The OpenChain client to fetch signatures from. + client: Option, } impl SignaturesIdentifier { @@ -42,7 +41,7 @@ impl SignaturesIdentifier { cache_path: Option, offline: bool, ) -> eyre::Result { - let sign_eth_api = OpenChainClient::new()?; + let client = if !offline { Some(OpenChainClient::new()?) } else { None }; let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); @@ -57,20 +56,13 @@ impl SignaturesIdentifier { } CachedSignatures::default() }; - Self { - cached, - cached_path: Some(path), - unavailable: HashSet::new(), - sign_eth_api, - offline, - } + Self { cached, cached_path: Some(path), unavailable: HashSet::new(), client } } else { Self { cached: Default::default(), cached_path: None, unavailable: HashSet::new(), - sign_eth_api, - offline, + client, } }; @@ -109,15 +101,14 @@ impl SignaturesIdentifier { let hex_identifiers: Vec = identifiers.into_iter().map(hex::encode_prefixed).collect(); - if !self.offline { + if let Some(client) = &self.client { let query: Vec<_> = hex_identifiers .iter() .filter(|v| !cache.contains_key(v.as_str())) .filter(|v| !self.unavailable.contains(v.as_str())) .collect(); - if let Ok(res) = self.sign_eth_api.decode_selectors(selector_type, query.clone()).await - { + if let Ok(res) = client.decode_selectors(selector_type, query.clone()).await { for (hex_id, selector_result) in query.into_iter().zip(res.into_iter()) { let mut found = false; if let Some(decoded_results) = selector_result { diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 1eb2af762..08f3e8d39 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -8,13 +8,22 @@ #[macro_use] extern crate tracing; -use alloy_primitives::{Address, Bytes, LogData}; +use std::collections::HashMap; + +use alloy_primitives::{Address, Bytes}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::constants::CHEATCODE_ADDRESS; -use futures::{future::BoxFuture, FutureExt}; +use revm::interpreter::OpCode; +use revm_inspectors::tracing::OpcodeFilter; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt::Write}; -use yansi::{Color, Paint}; + +pub use revm_inspectors::tracing::{ + types::{ + CallKind, CallLog, CallTrace, CallTraceNode, DecodedCallData, DecodedCallLog, + DecodedCallTrace, + }, + CallTraceArena, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, StackSnapshotType, + TraceWriter, TracingInspector, TracingInspectorConfig, +}; /// Call trace address identifiers. /// @@ -25,228 +34,29 @@ use identifier::{LocalTraceIdentifier, TraceIdentifier}; mod decoder; pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; -use revm_inspectors::tracing::types::LogCallOrder; -pub use revm_inspectors::tracing::{ - types::{CallKind, CallTrace, CallTraceNode}, - CallTraceArena, GethTraceBuilder, ParityTraceBuilder, StackSnapshotType, TracingInspector, - TracingInspectorConfig, -}; +pub mod debug; +pub use debug::DebugTraceIdentifier; pub type Traces = Vec<(TraceKind, CallTraceArena)>; -#[derive(Default, Debug, Eq, PartialEq)] -pub struct DecodedCallData { - pub signature: String, - pub args: Vec, -} - -#[derive(Default, Debug)] -pub struct DecodedCallTrace { - pub label: Option, - pub return_data: Option, - pub func: Option, - pub contract: Option, -} - -#[derive(Debug)] -pub enum DecodedCallLog<'a> { - /// A raw log. - Raw(&'a LogData), - /// A decoded log. - /// - /// The first member of the tuple is the event name, and the second is a vector of decoded - /// parameters. - Decoded(String, Vec<(String, String)>), -} - -const PIPE: &str = " │ "; -const EDGE: &str = " └─ "; -const BRANCH: &str = " ├─ "; -const CALL: &str = "→ "; -const RETURN: &str = "← "; - -/// Render a collection of call traces. +/// Decode a collection of call traces. /// /// The traces will be decoded using the given decoder, if possible. -pub async fn render_trace_arena( - arena: &CallTraceArena, +pub async fn decode_trace_arena( + arena: &mut CallTraceArena, decoder: &CallTraceDecoder, -) -> Result { +) -> Result<(), std::fmt::Error> { decoder.prefetch_signatures(arena.nodes()).await; + decoder.populate_traces(arena.nodes_mut()).await; - fn inner<'a>( - arena: &'a [CallTraceNode], - decoder: &'a CallTraceDecoder, - s: &'a mut String, - idx: usize, - left: &'a str, - child: &'a str, - ) -> BoxFuture<'a, Result<(), std::fmt::Error>> { - async move { - let node = &arena[idx]; - - // Display trace header - let (trace, return_data) = render_trace(&node.trace, decoder).await?; - writeln!(s, "{left}{trace}")?; - - // Display logs and subcalls - let left_prefix = format!("{child}{BRANCH}"); - let right_prefix = format!("{child}{PIPE}"); - for child in &node.ordering { - match child { - LogCallOrder::Log(index) => { - let log = render_trace_log(&node.logs[*index], decoder).await?; - - // Prepend our tree structure symbols to each line of the displayed log - log.lines().enumerate().try_for_each(|(i, line)| { - writeln!( - s, - "{}{}", - if i == 0 { &left_prefix } else { &right_prefix }, - line - ) - })?; - } - LogCallOrder::Call(index) => { - inner( - arena, - decoder, - s, - node.children[*index], - &left_prefix, - &right_prefix, - ) - .await?; - } - } - } - - // Display trace return data - let color = trace_color(&node.trace); - write!( - s, - "{child}{EDGE}{}{}", - RETURN.fg(color), - format!("[{:?}] ", node.trace.status).fg(color) - )?; - match return_data { - Some(val) => write!(s, "{val}"), - None if node.trace.kind.is_any_create() => { - write!(s, "{} bytes of code", node.trace.output.len()) - } - None if node.trace.output.is_empty() => Ok(()), - None => write!(s, "{}", node.trace.output), - }?; - writeln!(s)?; - - Ok(()) - } - .boxed() - } - - let mut s = String::new(); - inner(arena.nodes(), decoder, &mut s, 0, " ", " ").await?; - Ok(s) -} - -/// Render a call trace. -/// -/// The trace will be decoded using the given decoder, if possible. -pub async fn render_trace( - trace: &CallTrace, - decoder: &CallTraceDecoder, -) -> Result<(String, Option), std::fmt::Error> { - let mut s = String::new(); - write!(&mut s, "[{}] ", trace.gas_used)?; - let address = trace.address.to_checksum(None); - - let decoded = decoder.decode_function(trace).await; - if trace.kind.is_any_create() { - write!( - &mut s, - "{}{} {}@{}", - CALL.yellow(), - "new".yellow(), - decoded.label.as_deref().unwrap_or(""), - address - )?; - } else { - let (func_name, inputs) = match &decoded.func { - Some(DecodedCallData { signature, args }) => { - let name = signature.split('(').next().unwrap(); - (name.to_string(), args.join(", ")) - } - None => { - debug!(target: "evm::traces", trace=?trace, "unhandled raw calldata"); - if trace.data.len() < 4 { - ("fallback".to_string(), hex::encode(&trace.data)) - } else { - let (selector, data) = trace.data.split_at(4); - (hex::encode(selector), hex::encode(data)) - } - } - }; - - let action = match trace.kind { - CallKind::Call => "", - CallKind::StaticCall => " [staticcall]", - CallKind::CallCode => " [callcode]", - CallKind::DelegateCall => " [delegatecall]", - CallKind::Create | CallKind::Create2 => unreachable!(), - CallKind::AuthCall => " [authcall]", - }; - - let color = trace_color(trace); - write!( - &mut s, - "{addr}::{func_name}{opt_value}({inputs}){action}", - addr = decoded.label.as_deref().unwrap_or(&address).fg(color), - func_name = func_name.fg(color), - opt_value = if trace.value.is_zero() { - String::new() - } else { - format!("{{value: {}}}", trace.value) - }, - action = action.yellow(), - )?; - } - - Ok((s, decoded.return_data)) + Ok(()) } -/// Render a trace log. -async fn render_trace_log( - log: &LogData, - decoder: &CallTraceDecoder, -) -> Result { - let mut s = String::new(); - let decoded = decoder.decode_event(log).await; - - match decoded { - DecodedCallLog::Raw(log) => { - for (i, topic) in log.topics().iter().enumerate() { - writeln!( - s, - "{:>13}: {}", - if i == 0 { "emit topic 0".to_string() } else { format!("topic {i}") }, - format!("{topic:?}").cyan() - )?; - } - - write!(s, " data: {}", hex::encode_prefixed(&log.data).cyan())?; - } - DecodedCallLog::Decoded(name, params) => { - let params = params - .iter() - .map(|(name, value)| format!("{name}: {value}")) - .collect::>() - .join(", "); - - write!(s, "emit {}({params})", name.clone().cyan())?; - } - } - - Ok(s) +/// Render a collection of call traces to a string. +pub fn render_trace_arena(arena: &CallTraceArena) -> String { + let mut w = TraceWriter::new(Vec::::new()); + w.write_arena(arena).expect("Failed to write traces"); + String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } /// Specifies the kind of trace. @@ -283,17 +93,6 @@ impl TraceKind { } } -/// Chooses the color of the trace depending on the destination address and status of the call. -fn trace_color(trace: &CallTrace) -> Color { - if trace.address == CHEATCODE_ADDRESS { - Color::Blue - } else if trace.success { - Color::Green - } else { - Color::Red - } -} - /// Given a list of traces and artifacts, it returns a map connecting address to abi pub fn load_contracts<'a>( traces: impl IntoIterator, @@ -305,15 +104,120 @@ pub fn load_contracts<'a>( let decoder = CallTraceDecoder::new(); let mut contracts = ContractsByAddress::new(); for trace in traces { - let identified_addresses = - local_identifier.identify_addresses(decoder.trace_addresses(trace)); - for address in identified_addresses { - let contract = address.contract; - let abi = address.abi; - if let (Some(contract), Some(abi)) = (contract, abi) { + for address in local_identifier.identify_addresses(decoder.trace_addresses(trace)) { + if let (Some(contract), Some(abi)) = (address.contract, address.abi) { contracts.insert(address.address, (contract, abi.into_owned())); } } } contracts } + +/// Different kinds of internal functions tracing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum InternalTraceMode { + #[default] + None, + /// Traces internal functions without decoding inputs/outputs from memory. + Simple, + /// Same as `Simple`, but also tracks memory snapshots. + Full, +} + +impl From for TraceMode { + fn from(mode: InternalTraceMode) -> Self { + match mode { + InternalTraceMode::None => Self::None, + InternalTraceMode::Simple => Self::JumpSimple, + InternalTraceMode::Full => Self::Jump, + } + } +} + +// Different kinds of traces used by different foundry components. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum TraceMode { + /// Disabled tracing. + #[default] + None, + /// Simple call trace, no steps tracing required. + Call, + /// Call trace with tracing for JUMP and JUMPDEST opcode steps. + /// + /// Used for internal functions identification. Does not track memory snapshots. + JumpSimple, + /// Call trace with tracing for JUMP and JUMPDEST opcode steps. + /// + /// Same as `JumpSimple`, but tracks memory snapshots as well. + Jump, + /// Call trace with complete steps tracing. + /// + /// Used by debugger. + Debug, +} + +impl TraceMode { + pub const fn is_none(self) -> bool { + matches!(self, Self::None) + } + + pub const fn is_call(self) -> bool { + matches!(self, Self::Call) + } + + pub const fn is_jump_simple(self) -> bool { + matches!(self, Self::JumpSimple) + } + + pub const fn is_jump(self) -> bool { + matches!(self, Self::Jump) + } + + pub const fn is_debug(self) -> bool { + matches!(self, Self::Debug) + } + + pub fn with_debug(self, yes: bool) -> Self { + if yes { + std::cmp::max(self, Self::Debug) + } else { + self + } + } + + pub fn with_decode_internal(self, mode: InternalTraceMode) -> Self { + std::cmp::max(self, mode.into()) + } + + pub fn with_verbosity(self, verbosiy: u8) -> Self { + if verbosiy >= 3 { + std::cmp::max(self, Self::Call) + } else { + self + } + } + + pub fn into_config(self) -> Option { + if self.is_none() { + None + } else { + TracingInspectorConfig { + record_steps: self >= Self::JumpSimple, + record_memory_snapshots: self >= Self::Jump, + record_stack_snapshots: if self >= Self::JumpSimple { + StackSnapshotType::Full + } else { + StackSnapshotType::None + }, + record_logs: true, + record_state_diff: false, + record_returndata_snapshots: self.is_debug(), + record_opcodes_filter: (self.is_jump() || self.is_jump_simple()) + .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), + exclude_precompile_calls: false, + record_immediate_bytes: self.is_debug(), + } + .into() + } + } +} diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c138ed69c..c87bb6570 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -33,6 +33,7 @@ foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["full"] } foundry-config.workspace = true foundry-evm.workspace = true +foundry-evm-abi.workspace = true foundry-wallets.workspace = true foundry-linking.workspace = true foundry-zksync-core.workspace = true @@ -42,9 +43,9 @@ ethers-contract-abigen = { workspace = true, features = ["providers"] } revm-inspectors.workspace = true -comfy-table = "7" +comfy-table.workspace = true eyre.workspace = true -proptest = "1" +proptest.workspace = true rayon.workspace = true serde.workspace = true tracing.workspace = true @@ -81,7 +82,6 @@ clap_complete_fig = "4" dialoguer = { version = "0.11", default-features = false } dunce.workspace = true futures.workspace = true -hex.workspace = true indicatif = "0.17" itertools.workspace = true once_cell.workspace = true @@ -138,7 +138,7 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features "rustls", ] } tempfile.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } alloy-signer-local.workspace = true @@ -154,6 +154,7 @@ openssl = ["foundry-cli/openssl", "reqwest/default-tls"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["dep:tikv-jemallocator"] aws-kms = ["foundry-wallets/aws-kms"] +isolate-by-default = ["foundry-config/isolate-by-default"] [[bench]] name = "test" diff --git a/crates/forge/assets/CounterTemplate.s.sol b/crates/forge/assets/CounterTemplate.s.sol index df9ee8b02..cdc1fe9a1 100644 --- a/crates/forge/assets/CounterTemplate.s.sol +++ b/crates/forge/assets/CounterTemplate.s.sol @@ -2,11 +2,18 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; contract CounterScript is Script { + Counter public counter; + function setUp() public {} function run() public { - vm.broadcast(); + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); } } diff --git a/crates/forge/assets/workflowTemplate.yml b/crates/forge/assets/workflowTemplate.yml index 9282e8294..762a2966f 100644 --- a/crates/forge/assets/workflowTemplate.yml +++ b/crates/forge/assets/workflowTemplate.yml @@ -1,6 +1,9 @@ -name: test +name: CI -on: workflow_dispatch +on: + push: + pull_request: + workflow_dispatch: env: FOUNDRY_PROFILE: ci @@ -22,9 +25,17 @@ jobs: with: version: nightly - - name: Run Forge build + - name: Show Forge version run: | forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | forge build --sizes id: build diff --git a/crates/forge/bin/cmd/bind_json.rs b/crates/forge/bin/cmd/bind_json.rs new file mode 100644 index 000000000..bd2d0ea30 --- /dev/null +++ b/crates/forge/bin/cmd/bind_json.rs @@ -0,0 +1,539 @@ +use super::eip712::Resolver; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; +use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_compilers::{ + artifacts::{ + output_selection::OutputSelection, ContractDefinitionPart, Source, SourceUnit, + SourceUnitPart, Sources, + }, + multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, + project::ProjectCompiler, + solc::SolcLanguage, + CompilerSettings, Graph, Project, +}; +use foundry_config::Config; +use itertools::Itertools; +use rayon::prelude::*; +use solang_parser::pt as solang_ast; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, + fmt::Write, + path::PathBuf, + sync::Arc, +}; + +foundry_config::impl_figment_convert!(BindJsonArgs, opts); + +/// CLI arguments for `forge bind-json`. +#[derive(Clone, Debug, Parser)] +pub struct BindJsonArgs { + /// The path to write bindings to. + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] + pub out: Option, + + #[command(flatten)] + opts: CoreBuildArgs, +} + +impl BindJsonArgs { + pub fn run(self) -> Result<()> { + self.preprocess()?.compile()?.find_structs()?.resolve_imports_and_aliases().write()?; + + Ok(()) + } + + /// In cases when user moves/renames/deletes structs, compiler will start failing because + /// generated bindings will be referencing non-existing structs or importing non-existing + /// files. + /// + /// Because of that, we need a little bit of preprocessing to make sure that bindings will still + /// be valid. + /// + /// The strategy is: + /// 1. Replace bindings file with an empty one to get rid of potentially invalid imports. + /// 2. Remove all function bodies to get rid of `serialize`/`deserialize` invocations. + /// 3. Remove all `immutable` attributes to avoid errors because of erased constructors + /// initializing them. + /// + /// After that we'll still have enough information for bindings but compilation should succeed + /// in most of the cases. + fn preprocess(self) -> Result { + let config = self.try_load_config_emit_warnings()?; + let project = config.create_project(false, true)?; + + let target_path = config.root.0.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); + + let sources = project.paths.read_input_files()?; + let graph = Graph::::resolve_sources(&project.paths, sources)?; + + // We only generate bindings for a single Solidity version to avoid conflicts. + let mut sources = graph + // resolve graph into mapping language -> version -> sources + .into_sources_by_version(project.offline, &project.locked_versions, &project.compiler)? + .0 + .into_iter() + // we are only interested in Solidity sources + .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity)) + .ok_or_else(|| eyre::eyre!("no Solidity sources"))? + .1 + .into_iter() + // For now, we are always picking the latest version. + .max_by(|(v1, _), (v2, _)| v1.cmp(v2)) + .unwrap() + .1; + + // Insert empty bindings file + sources.insert(target_path.clone(), Source::new("library JsonBindings {}")); + + let sources = Sources( + sources + .0 + .into_par_iter() + .map(|(path, source)| { + let mut locs_to_update = Vec::new(); + let mut content = Arc::unwrap_or_clone(source.content); + let (parsed, _) = solang_parser::parse(&content, 0) + .map_err(|errors| eyre::eyre!("Parser failed: {errors:?}"))?; + + // All function definitions in the file + let mut functions = Vec::new(); + + for part in &parsed.0 { + if let solang_ast::SourceUnitPart::FunctionDefinition(def) = part { + functions.push(def); + } + if let solang_ast::SourceUnitPart::ContractDefinition(contract) = part { + for part in &contract.parts { + match part { + solang_ast::ContractPart::FunctionDefinition(def) => { + functions.push(def); + } + // Remove `immutable` attributes + solang_ast::ContractPart::VariableDefinition(def) => { + for attr in &def.attrs { + if let solang_ast::VariableAttribute::Immutable(loc) = + attr + { + locs_to_update.push(( + loc.start(), + loc.end(), + String::new(), + )); + } + } + } + _ => {} + } + } + }; + } + + for def in functions { + // If there's no body block, keep the function as is + let Some(solang_ast::Statement::Block { loc, .. }) = def.body else { + continue; + }; + let new_body = match def.ty { + solang_ast::FunctionTy::Modifier => "{ _; }", + _ => "{ revert(); }", + }; + let start = loc.start(); + let end = loc.end(); + locs_to_update.push((start, end + 1, new_body.to_string())); + } + + locs_to_update.sort_by_key(|(start, _, _)| *start); + + let mut shift = 0_i64; + + for (start, end, new) in locs_to_update { + let start = ((start as i64) - shift) as usize; + let end = ((end as i64) - shift) as usize; + + content.replace_range(start..end, new.as_str()); + shift += (end - start) as i64; + shift -= new.len() as i64; + } + + Ok((path, Source::new(content))) + }) + .collect::>>()?, + ); + + Ok(PreprocessedState { sources, target_path, project, config }) + } +} + +/// A single struct definition for which we need to generate bindings. +#[derive(Debug, Clone)] +struct StructToWrite { + /// Name of the struct definition. + name: String, + /// Name of the contract containing the struct definition. None if the struct is defined at the + /// file level. + contract_name: Option, + /// Import alias for the contract or struct, depending on whether the struct is imported + /// directly, or via a contract. + import_alias: Option, + /// Path to the file containing the struct definition. + path: PathBuf, + /// EIP712 schema for the struct. + schema: String, + /// Name of the struct definition used in function names and schema_* variables. + name_in_fns: String, +} + +impl StructToWrite { + /// Returns the name of the imported item. If struct is definied at the file level, returns the + /// struct name, otherwise returns the parent contract name. + fn struct_or_contract_name(&self) -> &str { + self.contract_name.as_deref().unwrap_or(&self.name) + } + + /// Same as [StructToWrite::struct_or_contract_name] but with alias applied. + fn struct_or_contract_name_with_alias(&self) -> &str { + self.import_alias.as_deref().unwrap_or(self.struct_or_contract_name()) + } + + /// Path which can be used to reference this struct in input/output parameters. Either + /// StructName or ParantName.StructName + fn full_path(&self) -> String { + if self.contract_name.is_some() { + format!("{}.{}", self.struct_or_contract_name_with_alias(), self.name) + } else { + self.struct_or_contract_name_with_alias().to_string() + } + } + + fn import_item(&self) -> String { + if let Some(alias) = &self.import_alias { + format!("{} as {}", self.struct_or_contract_name(), alias) + } else { + self.struct_or_contract_name().to_string() + } + } +} + +#[derive(Debug)] +struct PreprocessedState { + sources: Sources, + target_path: PathBuf, + project: Project, + config: Config, +} + +impl PreprocessedState { + fn compile(self) -> Result { + let Self { sources, target_path, mut project, config } = self; + + project.settings.update_output_selection(|selection| { + *selection = OutputSelection::ast_output_selection(); + }); + + let output = with_compilation_reporter(false, || { + ProjectCompiler::with_sources(&project, sources)?.compile() + })?; + + if output.has_compiler_errors() { + eyre::bail!("{output}"); + } + + // Collect ASTs by getting them from sources and converting into strongly typed + // `SourceUnit`s. Also strips root from paths. + let asts = output + .into_output() + .sources + .into_iter() + .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) + .map(|(path, ast)| { + Ok(( + path.strip_prefix(project.root()).unwrap_or(&path).to_path_buf(), + serde_json::from_str::(&serde_json::to_string(&ast)?)?, + )) + }) + .collect::>>()?; + + Ok(CompiledState { asts, target_path, config, project }) + } +} + +#[derive(Debug, Clone)] +struct CompiledState { + asts: BTreeMap, + target_path: PathBuf, + config: Config, + project: Project, +} + +impl CompiledState { + fn find_structs(self) -> Result { + let Self { asts, target_path, config, project } = self; + + // construct mapping (file, id) -> (struct definition, optional parent contract name) + let structs = asts + .iter() + .flat_map(|(path, ast)| { + let mut structs = Vec::new(); + // we walk AST directly instead of using visitors because we need to distinguish + // between file-level and contract-level struct definitions + for node in &ast.nodes { + match node { + SourceUnitPart::StructDefinition(def) => { + structs.push((def, None)); + } + SourceUnitPart::ContractDefinition(contract) => { + for node in &contract.nodes { + if let ContractDefinitionPart::StructDefinition(def) = node { + structs.push((def, Some(contract.name.clone()))); + } + } + } + _ => {} + } + } + structs.into_iter().map(|(def, parent)| ((path.as_path(), def.id), (def, parent))) + }) + .collect::>(); + + // Resolver for EIP712 schemas + let resolver = Resolver::new(&asts); + + let mut structs_to_write = Vec::new(); + + let include = config.bind_json.include; + let exclude = config.bind_json.exclude; + + for ((path, id), (def, contract_name)) in structs { + // For some structs there's no schema (e.g. if they contain a mapping), so we just skip + // those. + let Some(schema) = resolver.resolve_struct_eip712(id, &mut Default::default(), true)? + else { + continue + }; + + if !include.is_empty() { + if !include.iter().any(|matcher| matcher.is_match(path)) { + continue; + } + } else { + // Exclude library files by default + if project.paths.has_library_ancestor(path) { + continue; + } + } + + if exclude.iter().any(|matcher| matcher.is_match(path)) { + continue; + } + + structs_to_write.push(StructToWrite { + name: def.name.clone(), + contract_name, + path: path.to_path_buf(), + schema, + + // will be filled later + import_alias: None, + name_in_fns: String::new(), + }) + } + + Ok(StructsState { structs_to_write, target_path }) + } +} + +#[derive(Debug)] +struct StructsState { + structs_to_write: Vec, + target_path: PathBuf, +} + +impl StructsState { + /// We manage 2 namespsaces for JSON bindings: + /// - Namespace of imported items. This includes imports of contracts containing structs and + /// structs defined at the file level. + /// - Namespace of struct names used in function names and schema_* variables. + /// + /// Both of those might contain conflicts, so we need to resolve them. + fn resolve_imports_and_aliases(self) -> ResolvedState { + let Self { mut structs_to_write, target_path } = self; + + // firstly, we resolve imported names conflicts + // construct mapping name -> paths from which items with such name are imported + let mut names_to_paths = BTreeMap::new(); + + for s in &structs_to_write { + names_to_paths + .entry(s.struct_or_contract_name()) + .or_insert_with(BTreeSet::new) + .insert(s.path.as_path()); + } + + // now resolve aliases for names which need them and construct mapping (name, file) -> alias + let mut aliases = BTreeMap::new(); + + for (name, paths) in names_to_paths { + if paths.len() <= 1 { + // no alias needed + continue + } + + for (i, path) in paths.into_iter().enumerate() { + aliases + .entry(name.to_string()) + .or_insert_with(BTreeMap::new) + .insert(path.to_path_buf(), format!("{name}_{i}")); + } + } + + for s in &mut structs_to_write { + let name = s.struct_or_contract_name(); + if aliases.contains_key(name) { + s.import_alias = Some(aliases[name][&s.path].clone()); + } + } + + // Each struct needs a name by which we are referencing it in function names (e.g. + // deserializeFoo) Those might also have conflicts, so we manage a separate + // namespace for them + let mut name_to_structs_indexes = BTreeMap::new(); + + for (idx, s) in structs_to_write.iter().enumerate() { + name_to_structs_indexes.entry(&s.name).or_insert_with(Vec::new).push(idx); + } + + // Keeps `Some` for structs that will be referenced by name other than their definition + // name. + let mut fn_names = vec![None; structs_to_write.len()]; + + for (name, indexes) in name_to_structs_indexes { + if indexes.len() > 1 { + for (i, idx) in indexes.into_iter().enumerate() { + fn_names[idx] = Some(format!("{name}_{i}")); + } + } + } + + for (s, fn_name) in structs_to_write.iter_mut().zip(fn_names.into_iter()) { + s.name_in_fns = fn_name.unwrap_or(s.name.clone()); + } + + ResolvedState { structs_to_write, target_path } + } +} + +struct ResolvedState { + structs_to_write: Vec, + target_path: PathBuf, +} + +impl ResolvedState { + fn write(self) -> Result { + let mut result = String::new(); + self.write_imports(&mut result)?; + self.write_vm(&mut result); + self.write_library(&mut result)?; + + if let Some(parent) = self.target_path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&self.target_path, &result)?; + + println!("Bindings written to {}", self.target_path.display()); + + Ok(result) + } + + fn write_imports(&self, result: &mut String) -> fmt::Result { + let mut grouped_imports = BTreeMap::new(); + + for struct_to_write in &self.structs_to_write { + let item = struct_to_write.import_item(); + grouped_imports + .entry(struct_to_write.path.as_path()) + .or_insert_with(BTreeSet::new) + .insert(item); + } + + result.push_str("// Automatically generated by forge bind-json.\n\npragma solidity >=0.6.2 <0.9.0;\npragma experimental ABIEncoderV2;\n\n"); + + for (path, names) in grouped_imports { + writeln!( + result, + "import {{{}}} from \"{}\";", + names.iter().join(", "), + path.display() + )?; + } + + Ok(()) + } + + /// Writes minimal VM interface to not depend on forge-std version + fn write_vm(&self, result: &mut String) { + result.push_str(r#" +interface Vm { + function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory); + function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json); +} + "#); + } + + fn write_library(&self, result: &mut String) -> fmt::Result { + result.push_str( + r#" +library JsonBindings { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + +"#, + ); + // write schema constants + for struct_to_write in &self.structs_to_write { + writeln!( + result, + " string constant schema_{} = \"{}\";", + struct_to_write.name_in_fns, struct_to_write.schema + )?; + } + + // write serialization functions + for struct_to_write in &self.structs_to_write { + write!( + result, + r#" + function serialize({path} memory value) internal pure returns (string memory) {{ + return vm.serializeJsonType(schema_{name_in_fns}, abi.encode(value)); + }} + + function serialize({path} memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {{ + return vm.serializeJsonType(objectKey, valueKey, schema_{name_in_fns}, abi.encode(value)); + }} + + function deserialize{name_in_fns}(string memory json) public pure returns ({path} memory) {{ + return abi.decode(vm.parseJsonType(json, schema_{name_in_fns}), ({path})); + }} + + function deserialize{name_in_fns}(string memory json, string memory path) public pure returns ({path} memory) {{ + return abi.decode(vm.parseJsonType(json, path, schema_{name_in_fns}), ({path})); + }} + + function deserialize{name_in_fns}Array(string memory json, string memory path) public pure returns ({path}[] memory) {{ + return abi.decode(vm.parseJsonTypeArray(json, path, schema_{name_in_fns}), ({path}[])); + }} +"#, + name_in_fns = struct_to_write.name_in_fns, + path = struct_to_write.full_path() + )?; + } + + result.push_str("}\n"); + + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index bcbe335ec..703b9e6ff 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -93,7 +93,10 @@ impl BuildArgs { let mut files = vec![]; if let Some(paths) = self.paths { for path in paths { - files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS)); + files.extend(source_files_iter( + path.as_path(), + MultiCompilerLanguage::FILE_EXTENSIONS, + )); } } diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index 97f6383e3..fdf5f0888 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -289,6 +289,8 @@ impl CloneArgs { /// - `auto_detect_solc` to `false` /// - `solc_version` to the value from the metadata /// - `evm_version` to the value from the metadata +/// - `evm_version` to the value from the metadata, if the metadata's evm_version is "Default", then +/// this is derived from the solc version this contract was compiled with. /// - `via_ir` to the value from the metadata /// - `libraries` to the value from the metadata /// - `metadata` to the value from the metadata @@ -571,7 +573,7 @@ pub fn find_main_contract<'a>( rv = Some((PathBuf::from(f), a)); } } - rv.ok_or(eyre::eyre!("contract not found")) + rv.ok_or_else(|| eyre::eyre!("contract not found")) } #[cfg(test)] @@ -611,9 +613,9 @@ impl EtherscanClient for Client { #[cfg(test)] mod tests { use super::*; + use alloy_primitives::hex; use foundry_compilers::Artifact; use foundry_test_utils::rpc::next_etherscan_api_key; - use hex::ToHex; use std::collections::BTreeMap; fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { @@ -631,9 +633,9 @@ mod tests { if name == contract_name { let compiled_creation_code = contract.get_bytecode_object().expect("creation code not found"); - let compiled_creation_code: String = compiled_creation_code.encode_hex(); assert!( - compiled_creation_code.starts_with(stripped_creation_code), + hex::encode(compiled_creation_code.as_ref()) + .starts_with(stripped_creation_code), "inconsistent creation code" ); } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index cfea25043..55d47f0ab 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -27,7 +27,11 @@ use foundry_zksync_compiler::DualCompiledContracts; use rayon::prelude::*; use rustc_hash::FxHashMap; use semver::Version; -use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, +}; use yansi::Paint; // Loads project's figment and merges the build cli arguments into it @@ -91,7 +95,7 @@ impl CoverageArgs { let report = self.prepare(&project, &output)?; p_println!(!self.test.build_args().silent => "Running tests..."); - self.collect(project, output, report, Arc::new(config), evm_opts).await + self.collect(project, &output, report, Arc::new(config), evm_opts).await } /// Builds the project. @@ -124,7 +128,8 @@ impl CoverageArgs { // 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 = project.settings.solc.with_via_ir_minimum_optimization() + project.settings.solc.settings = + project.settings.solc.settings.with_via_ir_minimum_optimization() } else { project.settings.solc.optimizer.disable(); project.settings.solc.optimizer.runs = None; @@ -223,7 +228,7 @@ impl CoverageArgs { async fn collect( self, project: Project, - output: ProjectCompileOutput, + output: &ProjectCompileOutput, mut report: CoverageReport, config: Arc, evm_opts: EvmOpts, @@ -244,14 +249,15 @@ impl CoverageArgs { ..Default::default() }) .set_coverage(true) - .build(&root, output, None, env, evm_opts, DualCompiledContracts::default())?; + .build(&root, output.clone(), None, env, evm_opts, DualCompiledContracts::default())?; let known_contracts = runner.known_contracts.clone(); - let outcome = self - .test - .run_tests(runner, config.clone(), verbosity, &self.test.filter(&config)) - .await?; + let filter = self.test.filter(&config); + let outcome = + self.test.run_tests(runner, config.clone(), verbosity, &filter, output).await?; + + outcome.ensure_ok()?; // Add hit data to the coverage report let data = outcome.results.iter().flat_map(|(_, suite)| { @@ -287,6 +293,15 @@ 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.map_or(true, |re| { + !re.is_match(&path.strip_prefix(file_root).unwrap_or(path).to_string_lossy()) + }) + }); + // Output final report for report_kind in self.report { match report_kind { diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 1c6612e87..b0468df1c 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -107,6 +107,7 @@ impl CreateArgs { pub async fn run(mut self) -> Result<()> { // Find Project & Compile let project = self.opts.project()?; + let zksync = self.opts.compiler.zk.enabled(); if zksync { let target_path = if let Some(ref mut path) = self.contract.path { @@ -179,9 +180,8 @@ impl CreateArgs { let mut abs_path_buf = PathBuf::new(); abs_path_buf.push(project.root()); abs_path_buf.push(contract_path); - let abs_path_str = abs_path_buf.to_string_lossy(); let fdep_art = - zk_output.find(abs_path_str, contract_name).unwrap_or_else(|| { + zk_output.find(&abs_path_buf, contract_name).unwrap_or_else(|| { panic!( "Could not find contract {contract_name} at path {contract_path} for compilation output", ) diff --git a/crates/forge/bin/cmd/eip712.rs b/crates/forge/bin/cmd/eip712.rs new file mode 100644 index 000000000..4fa2a165f --- /dev/null +++ b/crates/forge/bin/cmd/eip712.rs @@ -0,0 +1,241 @@ +use clap::{Parser, ValueHint}; +use eyre::{Ok, OptionExt, Result}; +use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + artifacts::{ + output_selection::OutputSelection, + visitor::{Visitor, Walk}, + ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, + TypeName, + }, + CompilerSettings, +}; +use std::{collections::BTreeMap, path::PathBuf}; + +foundry_config::impl_figment_convert!(Eip712Args, opts); + +/// CLI arguments for `forge eip712`. +#[derive(Clone, Debug, Parser)] +pub struct Eip712Args { + /// The path to the file from which to read struct definitions. + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] + pub target_path: PathBuf, + + #[command(flatten)] + opts: CoreBuildArgs, +} + +impl Eip712Args { + pub fn run(self) -> Result<()> { + let config = self.try_load_config_emit_warnings()?; + let mut project = config.create_project(false, true)?; + let target_path = dunce::canonicalize(self.target_path)?; + project.settings.update_output_selection(|selection| { + *selection = OutputSelection::ast_output_selection(); + }); + + let output = ProjectCompiler::new().files([target_path.clone()]).compile(&project)?; + + // Collect ASTs by getting them from sources and converting into strongly typed + // `SourceUnit`s. + let asts = output + .into_output() + .sources + .into_iter() + .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) + .map(|(path, ast)| { + Ok((path, serde_json::from_str::(&serde_json::to_string(&ast)?)?)) + }) + .collect::>>()?; + + let resolver = Resolver::new(&asts); + + let target_ast = asts + .get(&target_path) + .ok_or_else(|| eyre::eyre!("Could not find AST for target file {target_path:?}"))?; + + let structs_in_target = { + let mut collector = StructCollector::default(); + target_ast.walk(&mut collector); + collector.0 + }; + + for (id, _) in structs_in_target { + if let Some(resolved) = + resolver.resolve_struct_eip712(id, &mut Default::default(), true)? + { + println!("{resolved}"); + println!(); + } + } + + Ok(()) + } +} + +/// AST [Visitor] used for collecting struct definitions. +#[derive(Debug, Clone, Default)] +pub struct StructCollector(pub BTreeMap); + +impl Visitor for StructCollector { + fn visit_struct_definition(&mut self, def: &StructDefinition) { + self.0.insert(def.id, def.clone()); + } +} + +/// Collects mapping from AST id of type definition to representation of this type for EIP-712 +/// encoding. +/// +/// For now, maps contract definitions to `address` and enums to `uint8`. +#[derive(Debug, Clone, Default)] +struct SimpleCustomTypesCollector(BTreeMap); + +impl Visitor for SimpleCustomTypesCollector { + fn visit_contract_definition(&mut self, def: &ContractDefinition) { + self.0.insert(def.id, "address".to_string()); + } + + fn visit_enum_definition(&mut self, def: &EnumDefinition) { + self.0.insert(def.id, "uint8".to_string()); + } +} + +pub struct Resolver { + simple_types: BTreeMap, + structs: BTreeMap, +} + +impl Resolver { + pub fn new(asts: &BTreeMap) -> Self { + let simple_types = { + let mut collector = SimpleCustomTypesCollector::default(); + asts.values().for_each(|ast| ast.walk(&mut collector)); + + collector.0 + }; + + let structs = { + let mut collector = StructCollector::default(); + asts.values().for_each(|ast| ast.walk(&mut collector)); + collector.0 + }; + + Self { simple_types, structs } + } + + /// Converts a given struct definition into EIP-712 `encodeType` representation. + /// + /// Returns `None` if struct contains any fields that are not supported by EIP-712 (e.g. + /// mappings or function pointers). + pub fn resolve_struct_eip712( + &self, + id: usize, + subtypes: &mut BTreeMap, + append_subtypes: bool, + ) -> Result> { + let def = &self.structs[&id]; + let mut result = format!("{}(", def.name); + + for (idx, member) in def.members.iter().enumerate() { + let Some(ty) = self.resolve_type( + member.type_name.as_ref().ok_or_eyre("missing type name")?, + subtypes, + )? + else { + return Ok(None) + }; + + result.push_str(&ty); + result.push(' '); + result.push_str(&member.name); + + if idx < def.members.len() - 1 { + result.push(','); + } + } + + result.push(')'); + + if !append_subtypes { + return Ok(Some(result)) + } + + for subtype_id in subtypes.values().copied().collect::>() { + if subtype_id == id { + continue + } + let Some(encoded_subtype) = self.resolve_struct_eip712(subtype_id, subtypes, false)? + else { + return Ok(None) + }; + result.push_str(&encoded_subtype); + } + + Ok(Some(result)) + } + + /// Converts given [TypeName] into a type which can be converted to [DynSolType]. + /// + /// Returns `None` if the type is not supported for EIP712 encoding. + pub fn resolve_type( + &self, + type_name: &TypeName, + subtypes: &mut BTreeMap, + ) -> Result> { + match type_name { + TypeName::FunctionTypeName(_) | TypeName::Mapping(_) => Ok(None), + TypeName::ElementaryTypeName(ty) => Ok(Some(ty.name.clone())), + TypeName::ArrayTypeName(ty) => { + let Some(inner) = self.resolve_type(&ty.base_type, subtypes)? else { + return Ok(None) + }; + let len = parse_array_length(&ty.type_descriptions)?; + + Ok(Some(format!("{inner}[{}]", len.unwrap_or("")))) + } + TypeName::UserDefinedTypeName(ty) => { + if let Some(name) = self.simple_types.get(&(ty.referenced_declaration as usize)) { + Ok(Some(name.clone())) + } else if let Some(def) = self.structs.get(&(ty.referenced_declaration as usize)) { + let name = + // If we've already seen struct with this ID, just use assigned name. + if let Some((name, _)) = subtypes.iter().find(|(_, id)| **id == def.id) { + name.clone() + // Otherwise, try assigning a new name. + } else { + let mut i = 0; + let mut name = def.name.clone(); + while subtypes.contains_key(&name) { + i += 1; + name = format!("{}_{i}", def.name); + } + + subtypes.insert(name.clone(), def.id); + name + }; + + return Ok(Some(name)) + } else { + return Ok(None) + } + } + } + } +} + +fn parse_array_length(type_description: &TypeDescriptions) -> Result> { + let type_string = + type_description.type_string.as_ref().ok_or_eyre("missing typeString for array type")?; + let Some(inside_brackets) = + type_string.rsplit_once('[').and_then(|(_, right)| right.split(']').next()) + else { + eyre::bail!("failed to parse array type string: {type_string}") + }; + + if inside_brackets.is_empty() { + Ok(None) + } else { + Ok(Some(inside_brackets)) + } +} diff --git a/crates/forge/bin/cmd/flatten.rs b/crates/forge/bin/cmd/flatten.rs index c4a011337..f538be5a8 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -45,9 +45,8 @@ impl FlattenArgs { let target_path = dunce::canonicalize(target_path)?; - let flattener = with_compilation_reporter(build_args.silent, || { - Flattener::new(project.clone(), &target_path) - }); + let flattener = + with_compilation_reporter(true, || Flattener::new(project.clone(), &target_path)); let flattened = match flattener { Ok(flattener) => Ok(flattener.flatten()), @@ -55,7 +54,7 @@ impl FlattenArgs { // Fallback to the old flattening implementation if we couldn't compile the target // successfully. This would be the case if the target has invalid // syntax. (e.g. Solang) - project.paths.clone().with_language::().flatten(&target_path) + project.paths.with_language::().flatten(&target_path) } Err(FlattenerError::Other(err)) => Err(err), } diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index bcfae7769..c46704836 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -202,10 +202,7 @@ impl fmt::Display for Line { } } -fn format_diff_summary<'a, 'b, 'r>(name: &str, diff: &'r TextDiff<'a, 'b, '_, str>) -> String -where - 'r: 'a + 'b, -{ +fn format_diff_summary<'a>(name: &str, diff: &'a TextDiff<'a, 'a, '_, str>) -> String { let cap = 128; let mut diff_summary = String::with_capacity(cap); diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index f19bc1de2..1882eca60 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -201,7 +201,7 @@ fn init_git_repo(git: Git<'_>, no_commit: bool) -> Result<()> { fn init_vscode(root: &Path) -> Result<()> { let remappings_file = root.join("remappings.txt"); if !remappings_file.exists() { - let mut remappings = Remapping::find_many(root.join("lib")) + let mut remappings = Remapping::find_many(&root.join("lib")) .into_iter() .map(|r| r.into_relative(root).to_relative_remapping().to_string()) .collect::>(); diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index 1c782f8bd..ac0ed41b3 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,19 +1,23 @@ +use alloy_primitives::Address; use clap::Parser; use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use eyre::Result; +use eyre::{Context, Result}; +use forge::revm::primitives::Eof; use foundry_cli::opts::{CompilerArgs, CoreBuildArgs}; -use foundry_common::compile::ProjectCompiler; +use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof}; use foundry_compilers::{ artifacts::{ output_selection::{ BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, EvmOutputSelection, EwasmOutputSelection, }, - StorageLayout, + CompactBytecode, StorageLayout, }, info::ContractInfo, utils::canonicalize, }; +use once_cell::sync::Lazy; +use regex::Regex; use std::fmt; /// CLI arguments for `forge inspect`. @@ -37,7 +41,7 @@ pub struct InspectArgs { impl InspectArgs { pub fn run(self) -> Result<()> { - let Self { mut contract, field, build, pretty } = self; + let Self { contract, field, build, pretty } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); @@ -48,7 +52,7 @@ impl InspectArgs { } // Run Optimized? - let optimized = if let ContractArtifactField::AssemblyOptimized = field { + let optimized = if field == ContractArtifactField::AssemblyOptimized { true } else { build.compiler.optimize @@ -62,16 +66,16 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let mut compiler = ProjectCompiler::new().quiet(true); - if let Some(contract_path) = &mut contract.path { - let target_path = canonicalize(&*contract_path)?; - *contract_path = target_path.to_string_lossy().to_string(); - compiler = compiler.files([target_path]); - } - let output = compiler.compile(&project)?; + let compiler = ProjectCompiler::new().quiet(true); + let target_path = if let Some(path) = &contract.path { + canonicalize(project.root().join(path))? + } else { + project.find_contract_path(&contract.name)? + }; + let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact - let artifact = output.find_contract(&contract).ok_or_else(|| { + let artifact = output.remove(&target_path, &contract.name).ok_or_else(|| { eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") })?; @@ -111,10 +115,10 @@ impl InspectArgs { print_json(&artifact.devdoc)?; } ContractArtifactField::Ir => { - print_json_str(&artifact.ir, None)?; + print_yul(artifact.ir.as_deref(), self.pretty)?; } ContractArtifactField::IrOptimized => { - print_json_str(&artifact.ir_optimized, None)?; + print_yul(artifact.ir_optimized.as_deref(), self.pretty)?; } ContractArtifactField::Metadata => { print_json(&artifact.metadata)?; @@ -158,6 +162,12 @@ impl InspectArgs { } print_json(&out)?; } + ContractArtifactField::Eof => { + print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?; + } + ContractArtifactField::EofInit => { + print_eof(artifact.bytecode)?; + } }; Ok(()) @@ -212,6 +222,8 @@ pub enum ContractArtifactField { Ewasm, Errors, Events, + Eof, + EofInit, } macro_rules! impl_value_enum { @@ -298,6 +310,8 @@ impl_value_enum! { Ewasm => "ewasm" | "e-wasm", Errors => "errors" | "er", Events => "events" | "ev", + Eof => "eof" | "eof-container" | "eof-deployed", + EofInit => "eof-init" | "eof-initcode" | "eof-initcontainer", } } @@ -322,6 +336,10 @@ impl From for ContractOutputSelection { Caf::Ewasm => Self::Ewasm(EwasmOutputSelection::All), Caf::Errors => Self::Abi, Caf::Events => Self::Abi, + Caf::Eof => Self::Evm(EvmOutputSelection::DeployedByteCode( + DeployedBytecodeOutputSelection::All, + )), + Caf::EofInit => Self::Evm(EvmOutputSelection::ByteCode(BytecodeOutputSelection::All)), } } } @@ -345,7 +363,9 @@ impl PartialEq for ContractArtifactField { (Self::IrOptimized, Cos::IrOptimized) | (Self::Metadata, Cos::Metadata) | (Self::UserDoc, Cos::UserDoc) | - (Self::Ewasm, Cos::Ewasm(_)) + (Self::Ewasm, Cos::Ewasm(_)) | + (Self::Eof, Cos::Evm(Eos::DeployedByteCode(_))) | + (Self::EofInit, Cos::Evm(Eos::ByteCode(_))) ) } } @@ -369,6 +389,28 @@ fn print_json(obj: &impl serde::Serialize) -> Result<()> { } fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> { + println!("{}", get_json_str(obj, key)?); + Ok(()) +} + +fn print_yul(yul: Option<&str>, pretty: bool) -> Result<()> { + let Some(yul) = yul else { + eyre::bail!("Could not get IR output"); + }; + + static YUL_COMMENTS: Lazy = + Lazy::new(|| Regex::new(r"(///.*\n\s*)|(\s*/\*\*.*\*/)").unwrap()); + + if pretty { + println!("{}", YUL_COMMENTS.replace_all(yul, "")); + } else { + println!("{yul}"); + } + + Ok(()) +} + +fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result { let value = serde_json::to_value(obj)?; let mut value_ref = &value; if let Some(key) = key { @@ -376,10 +418,34 @@ fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> value_ref = value2; } } - match value_ref.as_str() { - Some(s) => println!("{s}"), - None => println!("{value_ref:#}"), + let s = match value_ref.as_str() { + Some(s) => s.to_string(), + None => format!("{value_ref:#}"), + }; + Ok(s) +} + +/// Pretty-prints bytecode decoded EOF. +fn print_eof(bytecode: Option) -> Result<()> { + let Some(mut bytecode) = bytecode else { eyre::bail!("No bytecode") }; + + // Replace link references with zero address. + if bytecode.object.is_unlinked() { + for (file, references) in bytecode.link_references.clone() { + for (name, _) in references { + bytecode.link(&file, &name, Address::ZERO); + } + } } + + let Some(bytecode) = bytecode.object.into_bytes() else { + eyre::bail!("Failed to link bytecode"); + }; + + let eof = Eof::decode(bytecode).wrap_err("Failed to decode EOF")?; + + println!("{}", pretty_eof(&eof)?); + Ok(()) } diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index d3e2f8b6d..ff63fa7cb 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -40,6 +40,7 @@ //! ``` pub mod bind; +pub mod bind_json; pub mod build; pub mod cache; pub mod clone; @@ -48,6 +49,7 @@ pub mod coverage; pub mod create; pub mod debug; pub mod doc; +pub mod eip712; pub mod flatten; pub mod fmt; pub mod geiger; diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index 81188f35b..c1626e99f 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -1,3 +1,4 @@ +use alloy_primitives::hex; use clap::Parser; use comfy_table::Table; use eyre::Result; @@ -177,7 +178,7 @@ impl SelectorsSubcommands { Self::List { contract, project_paths } => { println!("Listing selectors for contracts in the project..."); let build_args = CoreBuildArgs { - project_paths: project_paths.clone(), + project_paths, compiler: CompilerArgs { extra_output: vec![ContractOutputSelection::Abi], ..Default::default() diff --git a/crates/forge/bin/cmd/soldeer.rs b/crates/forge/bin/cmd/soldeer.rs index 52e1240a0..9c8579fe7 100644 --- a/crates/forge/bin/cmd/soldeer.rs +++ b/crates/forge/bin/cmd/soldeer.rs @@ -6,6 +6,9 @@ use soldeer::commands::Subcommands; // CLI arguments for `forge soldeer`. #[derive(Clone, Debug, Parser)] #[clap(override_usage = "forge soldeer install [DEPENDENCY]~[VERSION] + forge soldeer install [DEPENDENCY]~[VERSION] + forge soldeer install [DEPENDENCY]~[VERSION] --rev + forge soldeer install [DEPENDENCY]~[VERSION] --rev forge soldeer push [DEPENDENCY]~[VERSION] forge soldeer login forge soldeer update diff --git a/crates/forge/bin/cmd/test/filter.rs b/crates/forge/bin/cmd/test/filter.rs index 7ececa7d4..ec2e9b01b 100644 --- a/crates/forge/bin/cmd/test/filter.rs +++ b/crates/forge/bin/cmd/test/filter.rs @@ -1,5 +1,5 @@ use clap::Parser; -use forge::TestFilter; +use foundry_common::TestFilter; use foundry_compilers::{FileFilter, ProjectPathsConfig}; use foundry_config::{filter::GlobMatcher, Config}; use std::{fmt, path::Path}; @@ -38,6 +38,10 @@ pub struct FilterArgs { value_name = "GLOB" )] pub path_pattern_inverse: Option, + + /// Only show coverage for files that do not match the specified regex pattern. + #[arg(long = "no-match-coverage", visible_alias = "nmco", value_name = "REGEX")] + pub coverage_pattern_inverse: Option, } impl FilterArgs { @@ -71,6 +75,9 @@ impl FilterArgs { if self.path_pattern_inverse.is_none() { self.path_pattern_inverse = config.path_pattern_inverse.clone().map(Into::into); } + if self.coverage_pattern_inverse.is_none() { + self.coverage_pattern_inverse = config.coverage_pattern_inverse.clone().map(Into::into); + } ProjectPathsAwareFilter { args_filter: self, paths: config.project_paths() } } } @@ -84,6 +91,7 @@ impl fmt::Debug for FilterArgs { .field("no-match-contract", &self.contract_pattern_inverse.as_ref().map(|r| r.as_str())) .field("match-path", &self.path_pattern.as_ref().map(|g| g.as_str())) .field("no-match-path", &self.path_pattern_inverse.as_ref().map(|g| g.as_str())) + .field("no-match-coverage", &self.coverage_pattern_inverse.as_ref().map(|g| g.as_str())) .finish_non_exhaustive() } } @@ -152,6 +160,9 @@ impl fmt::Display for FilterArgs { if let Some(p) = &self.path_pattern_inverse { writeln!(f, "\tno-match-path: `{}`", p.as_str())?; } + if let Some(p) = &self.coverage_pattern_inverse { + writeln!(f, "\tno-match-coverage: `{}`", p.as_str())?; + } Ok(()) } } @@ -178,6 +189,11 @@ impl ProjectPathsAwareFilter { pub fn args_mut(&mut self) -> &mut FilterArgs { &mut self.args_filter } + + /// Returns the project paths. + pub fn paths(&self) -> &ProjectPathsConfig { + &self.paths + } } impl FileFilter for ProjectPathsAwareFilter { diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index d39e95e41..8c6f1d1a0 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -7,23 +7,24 @@ use forge::{ gas_report::GasReport, multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, - traces::{identifier::SignaturesIdentifier, CallTraceDecoderBuilder, TraceKind}, + traces::{ + debug::{ContractSources, DebugTraceIdentifier}, + decode_trace_arena, + identifier::SignaturesIdentifier, + render_trace_arena, CallTraceDecoderBuilder, InternalTraceMode, TraceKind, + }, MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder, }; use foundry_cli::{ opts::CoreBuildArgs, utils::{self, LoadConfig}, }; -use foundry_common::{ - compile::{ContractSources, ProjectCompiler}, - evm::EvmArgs, - shell, -}; +use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell}; use foundry_compilers::{ artifacts::output_selection::OutputSelection, compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language}, - solc::SolcLanguage, utils::source_files_iter, + ProjectCompileOutput, }; use foundry_config::{ figment, @@ -50,7 +51,6 @@ mod summary; use summary::TestSummaryReporter; pub use filter::FilterArgs; -use forge::traces::render_trace_arena; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); @@ -77,6 +77,20 @@ pub struct TestArgs { #[arg(long, value_name = "TEST_FUNCTION")] debug: Option, + /// Whether to identify internal functions in traces. + /// + /// If no argument is passed to this flag, it will trace internal functions scope and decode + /// stack parameters, but parameters stored in memory (such as bytes or arrays) will not be + /// decoded. + /// + /// To decode memory parameters, you should pass an argument with a test function name, + /// similarly to --debug and --match-test. + /// + /// If more than one test matches your specified criteria, you must add additional filters + /// until only one test is found (see --match-contract and --match-path). + #[arg(long, value_name = "TEST_FUNCTION")] + decode_internal: Option>, + /// Print a gas report. #[arg(long, env = "FORGE_GAS_REPORT")] gas_report: bool, @@ -86,7 +100,7 @@ pub struct TestArgs { allow_failure: bool, /// Output test results in JSON format. - #[arg(long, short, help_heading = "Display options")] + #[arg(long, help_heading = "Display options")] json: bool, /// Stop running tests after the first failure. @@ -114,12 +128,21 @@ pub struct TestArgs { /// Max concurrent threads to use. /// Default value is the number of available CPUs. + #[arg(long, short = 'j', visible_alias = "jobs")] + pub threads: Option, + + /// Show test execution progress. #[arg(long)] - pub max_threads: Option, + pub show_progress: bool, #[command(flatten)] filter: FilterArgs, + /// Re-run recorded test failures from last run. + /// If no failure recorded then regular test run is performed. + #[arg(long)] + pub rerun: bool, + #[command(flatten)] evm_opts: EvmArgs, @@ -136,10 +159,6 @@ pub struct TestArgs { /// Print detailed test summary table. #[arg(long, help_heading = "Display options", requires = "summary")] pub detailed: bool, - - /// Show test execution progress. - #[arg(long)] - pub show_progress: bool, } impl TestArgs { @@ -157,7 +176,7 @@ impl TestArgs { /// Returns sources which include any tests to be executed. /// If no filters are provided, sources are filtered by existence of test/invariant methods in /// them, If filters are provided, sources are additionaly filtered by them. - pub fn get_sources_to_compile( + pub fn get_sources_to_compile( &self, config: &Config, filter: &ProjectPathsAwareFilter, @@ -217,7 +236,10 @@ impl TestArgs { } // Always recompile all sources to ensure that `getCode` cheatcode can use any artifact. - test_sources.extend(source_files_iter(project.paths.sources, L::FILE_EXTENSIONS)); + test_sources.extend(source_files_iter( + &project.paths.sources, + MultiCompilerLanguage::FILE_EXTENSIONS, + )); Ok(test_sources) } @@ -229,17 +251,17 @@ impl TestArgs { /// /// Returns the test results for all matching tests. pub async fn execute_tests(self) -> Result { + // Merge all configs. + let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + // Set number of max threads to execute tests. // If not specified then the number of threads determined by rayon will be used. - if let Some(test_threads) = self.max_threads { + if let Some(test_threads) = config.threads { trace!(target: "forge::test", "execute tests with {} max threads", test_threads); - rayon::ThreadPoolBuilder::new().num_threads(test_threads as usize).build_global()?; + rayon::ThreadPoolBuilder::new().num_threads(test_threads).build_global()?; } - // Merge all configs - let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - - // Explicitly enable isolation for gas reports for more correct gas accounting + // Explicitly enable isolation for gas reports for more correct gas accounting. if self.gas_report { evm_opts.isolate = true; } else { @@ -263,8 +285,7 @@ impl TestArgs { let mut filter = self.filter(&config); trace!(target: "forge::test", ?filter, "using filter"); - let sources_to_compile = - self.get_sources_to_compile::(&config, &filter)?; + let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; let compiler = ProjectCompiler::new() .quiet_if(self.json || self.opts.silent) @@ -275,8 +296,7 @@ impl TestArgs { let (zk_output, dual_compiled_contracts) = if config.zksync.should_compile() { let zk_project = foundry_zksync_compiler::create_project(&config, config.cache, false)?; - let sources_to_compile = - self.get_sources_to_compile::(&config, &filter)?; + let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; let zk_compiler = ProjectCompiler::new() .quiet_if(self.json || self.opts.silent) .files(sources_to_compile); @@ -302,7 +322,7 @@ impl TestArgs { .profiles(profiles) .build(&output, project_root)?; - // Determine print verbosity and executor verbosity + // Determine print verbosity and executor verbosity. let verbosity = evm_opts.verbosity; if self.gas_report && evm_opts.verbosity < 3 { evm_opts.verbosity = 3; @@ -310,7 +330,20 @@ impl TestArgs { let env = evm_opts.evm_env().await?; - // Prepare the test builder + // Choose the internal function tracing mode, if --decode-internal is provided. + let decode_internal = if let Some(maybe_fn) = self.decode_internal.as_ref() { + if maybe_fn.is_some() { + // If function filter is provided, we enable full tracing. + InternalTraceMode::Full + } else { + // If no function filter is provided, we enable simple tracing. + InternalTraceMode::Simple + } + } else { + InternalTraceMode::None + }; + + // Prepare the test builder. let should_debug = self.debug.is_some(); // Clone the output only if we actually need it later for the debugger. @@ -320,6 +353,7 @@ impl TestArgs { let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) + .set_decode_internal(decode_internal) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) @@ -328,29 +362,39 @@ impl TestArgs { .enable_isolation(evm_opts.isolate) .build( project_root, - output, + output.clone(), zk_output, env, evm_opts, dual_compiled_contracts.unwrap_or_default(), )?; - if let Some(debug_test_pattern) = &self.debug { - let test_pattern = &mut filter.args_mut().test_pattern; - if test_pattern.is_some() { - eyre::bail!( - "Cannot specify both --debug and --match-test. \ - Use --match-contract and --match-path to further limit the search instead." - ); + let mut maybe_override_mt = |flag, maybe_regex: Option<&Regex>| { + if let Some(regex) = maybe_regex { + 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()); } - *test_pattern = Some(debug_test_pattern.clone()); - } + + Ok(()) + }; + + maybe_override_mt("debug", self.debug.as_ref())?; + maybe_override_mt( + "decode-internal", + self.decode_internal.as_ref().and_then(|v| v.as_ref()), + )?; let libraries = runner.libraries.clone(); - let outcome = self.run_tests(runner, config, verbosity, &filter).await?; + let outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; if should_debug { - // Get first non-empty suite result. We will have only one such entry + // Get first non-empty suite result. We will have only one such entry. let Some((_, test_result)) = outcome .results .iter() @@ -368,12 +412,15 @@ impl TestArgs { // Run the debugger. let mut builder = Debugger::builder() - .debug_arenas(test_result.debug.as_slice()) + .traces( + test_result.traces.iter().filter(|(t, _)| t.is_execution()).cloned().collect(), + ) .sources(sources) .breakpoints(test_result.breakpoints.clone()); if let Some(decoder) = &outcome.last_run_decoder { builder = builder.decoder(decoder); } + let mut debugger = builder.build(); debugger.try_run()?; } @@ -388,6 +435,7 @@ impl TestArgs { config: Arc, verbosity: u8, filter: &ProjectPathsAwareFilter, + output: &ProjectCompileOutput, ) -> eyre::Result { if self.list { return list(runner, filter, self.json); @@ -396,7 +444,9 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); let num_filtered = runner.matching_test_functions(filter).count(); - if self.debug.is_some() && num_filtered != 1 { + if (self.debug.is_some() || self.decode_internal.as_ref().map_or(false, |v| v.is_some())) && + num_filtered != 1 + { eyre::bail!( "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to run the debugger.\n\n\ Use --match-contract and --match-path to further limit the search.\n\ @@ -413,10 +463,12 @@ impl TestArgs { let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; let known_contracts = runner.known_contracts.clone(); + let libraries = runner.libraries.clone(); + // Run tests. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); - let show_progress = self.show_progress; + let show_progress = config.show_progress; let handle = tokio::task::spawn_blocking({ let filter = filter.clone(); move || runner.test(&filter, tx, show_progress) @@ -442,6 +494,12 @@ impl TestArgs { config.offline, )?); } + + if self.decode_internal.is_some() { + let sources = + ContractSources::from_project_output(output, &config.root, Some(&libraries))?; + builder = builder.with_debug_identifier(DebugTraceIdentifier::new(sources)); + } let mut decoder = builder.build(); let mut gas_report = self @@ -500,7 +558,7 @@ impl TestArgs { // Identify addresses and decode traces. let mut decoded_traces = Vec::with_capacity(result.traces.len()); - for (kind, arena) in &result.traces { + for (kind, arena) in &mut result.traces.clone() { if identify_addresses { decoder.identify(arena, &mut identifier); } @@ -521,7 +579,8 @@ impl TestArgs { }; if should_include { - decoded_traces.push(render_trace_arena(arena, &decoder).await?); + decode_trace_arena(arena, &decoder).await?; + decoded_traces.push(render_trace_arena(arena)); } } @@ -596,12 +655,20 @@ impl TestArgs { } } + // Persist test run failures to enable replaying. + persist_run_failures(&config, &outcome); + Ok(outcome) } /// Returns the flattened [`FilterArgs`] arguments merged with [`Config`]. + /// Loads and applies filter from file if only last test run failures performed. pub fn filter(&self, config: &Config) -> ProjectPathsAwareFilter { - self.filter.clone().merge_with_config(config) + let mut filter = self.filter.clone(); + if self.rerun { + filter.test_pattern = last_run_failures(config); + } + filter.merge_with_config(config) } /// Returns whether `BuildArgs` was configured with `--watch` @@ -645,6 +712,14 @@ impl Provider for TestArgs { dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } + if self.show_progress { + dict.insert("show_progress".to_string(), true.into()); + } + + if let Some(threads) = self.threads { + dict.insert("threads".to_string(), threads.into()); + } + Ok(Map::from([(Config::selected_profile(), dict)])) } } @@ -671,6 +746,31 @@ fn list( Ok(TestOutcome::empty(false)) } +/// Load persisted filter (with last test run failures) from file. +fn last_run_failures(config: &Config) -> Option { + match fs::read_to_string(&config.test_failures_file) { + Ok(filter) => Some(Regex::new(&filter).unwrap()), + Err(_) => None, + } +} + +/// Persist filter with last test run failures (only if there's any failure). +fn persist_run_failures(config: &Config, outcome: &TestOutcome) { + if outcome.failed() > 0 && fs::create_file(&config.test_failures_file).is_ok() { + let mut filter = String::new(); + let mut failures = outcome.failures().peekable(); + while let Some((test_name, _)) = failures.next() { + if let Some(test_match) = test_name.split('(').next() { + filter.push_str(test_match); + if failures.peek().is_some() { + filter.push('|'); + } + } + } + let _ = fs::write(&config.test_failures_file, filter); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index aff2ad530..4484be629 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -112,6 +112,8 @@ fn main() -> Result<()> { }, ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()), ForgeSubcommand::Soldeer(cmd) => cmd.run(), + ForgeSubcommand::Eip712(cmd) => cmd.run(), + ForgeSubcommand::BindJson(cmd) => cmd.run(), } } diff --git a/crates/forge/bin/opts.rs b/crates/forge/bin/opts.rs index a449bd75f..b86d19c17 100644 --- a/crates/forge/bin/opts.rs +++ b/crates/forge/bin/opts.rs @@ -1,8 +1,8 @@ use crate::cmd::{ - bind::BindArgs, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, config, coverage, - create::CreateArgs, debug::DebugArgs, doc::DocArgs, flatten, fmt::FmtArgs, geiger, generate, - init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs, remove::RemoveArgs, - selectors::SelectorsSubcommands, snapshot, soldeer, test, tree, update, + bind::BindArgs, bind_json, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, config, + coverage, create::CreateArgs, debug::DebugArgs, doc::DocArgs, eip712, flatten, fmt::FmtArgs, + geiger, generate, init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs, + remove::RemoveArgs, selectors::SelectorsSubcommands, snapshot, soldeer, test, tree, update, }; use clap::{Parser, Subcommand, ValueHint}; use forge_script::ScriptArgs; @@ -164,6 +164,12 @@ pub enum ForgeSubcommand { /// Soldeer dependency manager. Soldeer(soldeer::SoldeerArgs), + + /// Generate EIP-712 struct encodings for structs from a given file. + Eip712(eip712::Eip712Args), + + /// Generate bindings for serialization/deserialization of project structs via JSON cheatcodes. + BindJson(bind_json::BindJsonArgs), } #[cfg(test)] diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 8b06f3074..cab937488 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -116,10 +116,9 @@ impl<'a> CoverageReporter for LcovReporter<'a> { if hits == 0 { "-".to_string() } else { hits.to_string() } )?; } - // Statements are not in the LCOV format - CoverageItemKind::Statement => { - writeln!(self.destination, "DA:{line},{hits}")?; - } + // Statements are not in the LCOV format. + // We don't add them in order to avoid doubling line hits. + _ => {} } } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 058af0587..0c4c8263e 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -1,12 +1,13 @@ //! Gas reports. use crate::{ - constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, + constants::CHEATCODE_ADDRESS, traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use comfy_table::{presets::ASCII_MARKDOWN, *}; use foundry_common::{calc, TestFunctionExt}; use foundry_evm::traces::CallKind; +use foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashSet}, @@ -84,30 +85,30 @@ impl GasReport { if trace.depth > 1 && (trace.kind == CallKind::Call || trace.kind == CallKind::Create || - trace.kind == CallKind::Create2) + trace.kind == CallKind::Create2 || + trace.kind == CallKind::EOFCreate) { return; } - let decoded = decoder.decode_function(&node.trace).await; - - let Some(name) = &decoded.contract else { return }; + let Some(name) = decoder.contracts.get(&node.trace.address) else { return }; let contract_name = name.rsplit(':').next().unwrap_or(name); if !self.should_report(contract_name) { return; } + let decoded = || decoder.decode_function(&node.trace); + let contract_info = self.contracts.entry(name.to_string()).or_default(); if trace.kind.is_any_create() { trace!(contract_name, "adding create gas info"); contract_info.gas = trace.gas_used; contract_info.size = trace.data.len(); - } else if let Some(DecodedCallData { signature, .. }) = decoded.func { + } else if let Some(DecodedCallData { signature, .. }) = decoded().await.call_data { let name = signature.split('(').next().unwrap(); // ignore any test/setup functions - let should_include = !(name.is_test() || name.is_invariant_test() || name.is_setup()); - if should_include { + if !name.test_function_kind().is_known() { trace!(contract_name, signature, "adding gas info"); let gas_info = contract_info .functions @@ -144,7 +145,7 @@ impl Display for GasReport { for (name, contract) in &self.contracts { if contract.functions.is_empty() { trace!(name, "gas report contract without functions"); - continue + continue; } let mut table = Table::new(); diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index c46800067..6ad91ab66 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -60,28 +60,37 @@ impl TestOptions { let mut inline_invariant = InlineConfig::::default(); let mut inline_fuzz = InlineConfig::::default(); - for natspec in natspecs { - // Perform general validation - validate_profiles(&natspec, &profiles)?; - FuzzConfig::validate_configs(&natspec)?; - InvariantConfig::validate_configs(&natspec)?; + // Validate all natspecs + for natspec in &natspecs { + validate_profiles(natspec, &profiles)?; + } + + // Firstly, apply contract-level configurations + for natspec in natspecs.iter().filter(|n| n.function.is_none()) { + if let Some(fuzz) = base_fuzz.merge(natspec)? { + inline_fuzz.insert_contract(&natspec.contract, fuzz); + } + + if let Some(invariant) = base_invariant.merge(natspec)? { + inline_invariant.insert_contract(&natspec.contract, invariant); + } + } + for (natspec, f) in natspecs.iter().filter_map(|n| n.function.as_ref().map(|f| (n, f))) { // Apply in-line configurations for the current profile - let configs: Vec = natspec.current_profile_configs().collect(); - let c: &str = &natspec.contract; - let f: &str = &natspec.function; - let line: String = natspec.debug_context(); - - match base_fuzz.try_merge(&configs) { - Ok(Some(conf)) => inline_fuzz.insert(c, f, conf), - Ok(None) => { /* No inline config found, do nothing */ } - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + let c = &natspec.contract; + + // We might already have inserted contract-level configs above, so respect data already + // present in inline configs. + let base_fuzz = inline_fuzz.get(c, f).unwrap_or(&base_fuzz); + let base_invariant = inline_invariant.get(c, f).unwrap_or(&base_invariant); + + if let Some(fuzz) = base_fuzz.merge(natspec)? { + inline_fuzz.insert_fn(c, f, fuzz); } - match base_invariant.try_merge(&configs) { - Ok(Some(conf)) => inline_invariant.insert(c, f, conf), - Ok(None) => { /* No inline config found, do nothing */ } - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + if let Some(invariant) = base_invariant.merge(natspec)? { + inline_invariant.insert_fn(c, f, invariant); } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 9dcb584b3..83bed5ad8 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -16,8 +16,14 @@ use foundry_compilers::{ }; use foundry_config::Config; use foundry_evm::{ - backend::Backend, decode::RevertDecoder, executors::ExecutorBuilder, fork::CreateFork, - inspectors::CheatsConfig, opts::EvmOpts, revm, + backend::Backend, + decode::RevertDecoder, + executors::ExecutorBuilder, + fork::CreateFork, + inspectors::CheatsConfig, + opts::EvmOpts, + revm, + traces::{InternalTraceMode, TraceMode}, }; use foundry_linking::{LinkOutput, Linker}; use foundry_zksync_compiler::DualCompiledContracts; @@ -65,6 +71,8 @@ pub struct MultiContractRunner { pub coverage: bool, /// Whether to collect debug info pub debug: bool, + /// Whether to enable steps tracking in the tracer. + pub decode_internal: InternalTraceMode, /// Settings related to fuzz and/or invariant tests pub test_options: TestOptions, /// Whether to enable call isolation @@ -87,9 +95,7 @@ impl MultiContractRunner { &'a self, filter: &'a dyn TestFilter, ) -> impl Iterator { - self.contracts - .iter() - .filter(|&(id, TestContract { abi, .. })| matches_contract(id, abi, filter)) + self.contracts.iter().filter(|&(id, c)| matches_contract(id, &c.abi, filter)) } /// Returns an iterator over all test functions that match the filter. @@ -98,7 +104,7 @@ impl MultiContractRunner { filter: &'a dyn TestFilter, ) -> impl Iterator { self.matching_contracts(filter) - .flat_map(|(_, TestContract { abi, .. })| abi.functions()) + .flat_map(|(_, c)| c.abi.functions()) .filter(|func| is_matching_test(func, filter)) } @@ -110,17 +116,18 @@ impl MultiContractRunner { self.contracts .iter() .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) - .flat_map(|(_, TestContract { abi, .. })| abi.functions()) - .filter(|func| func.is_test() || func.is_invariant_test()) + .flat_map(|(_, c)| c.abi.functions()) + .filter(|func| func.is_any_test()) } /// Returns all matching tests grouped by contract grouped by file (file -> (contract -> tests)) pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap>> { self.matching_contracts(filter) - .map(|(id, TestContract { abi, .. })| { + .map(|(id, c)| { let source = id.source.as_path().display().to_string(); let name = id.name.clone(); - let tests = abi + let tests = c + .abi .functions() .filter(|func| is_matching_test(func, filter)) .map(|func| func.name.clone()) @@ -168,7 +175,7 @@ impl MultiContractRunner { tx: mpsc::Sender<(String, SuiteResult)>, show_progress: bool, ) { - let handle = tokio::runtime::Handle::current(); + let tokio_handle = tokio::runtime::Handle::current(); trace!("running all tests"); // The DB backend that serves all the data. @@ -191,7 +198,7 @@ impl MultiContractRunner { let results: Vec<(String, SuiteResult)> = contracts .par_iter() .map(|&(id, contract)| { - let _guard = handle.enter(); + let _guard = tokio_handle.enter(); tests_progress.inner.lock().start_suite_progress(&id.identifier()); let result = self.run_test_suite( @@ -199,7 +206,7 @@ impl MultiContractRunner { contract, db.clone(), filter, - &handle, + &tokio_handle, Some(&tests_progress), ); @@ -219,8 +226,9 @@ impl MultiContractRunner { }); } else { contracts.par_iter().for_each(|&(id, contract)| { - let _guard = handle.enter(); - let result = self.run_test_suite(id, contract, db.clone(), filter, &handle, None); + let _guard = tokio_handle.enter(); + let result = + self.run_test_suite(id, contract, db.clone(), filter, &tokio_handle, None); let _ = tx.send((id.identifier(), result)); }) } @@ -232,7 +240,7 @@ impl MultiContractRunner { contract: &TestContract, db: Backend, filter: &dyn TestFilter, - handle: &tokio::runtime::Handle, + tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, ) -> SuiteResult { let identifier = artifact_id.identifier(); @@ -248,39 +256,47 @@ impl MultiContractRunner { self.use_zk, ); + let trace_mode = TraceMode::default() + .with_debug(self.debug) + .with_decode_internal(self.decode_internal) + .with_verbosity(self.evm_opts.verbosity); + let executor = ExecutorBuilder::new() .inspectors(|stack| { stack .cheatcodes(Arc::new(cheats_config)) - .trace(self.evm_opts.verbosity >= 3 || self.debug) - .debug(self.debug) + .trace_mode(trace_mode) .coverage(self.coverage) .enable_isolation(self.isolation) }) .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) + .legacy_assertions(self.config.legacy_assertions) .build(self.env.clone(), db); if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); } - let _guard = debug_span!("suite", name = span_name).entered(); + let span = debug_span!("suite", name = %span_name); + let span_local = span.clone(); + let _guard = span_local.enter(); debug!("start executing all tests in contract"); - let runner = ContractRunner::new( - &identifier, - executor, + let runner = ContractRunner { + name: &identifier, contract, - &self.libs_to_deploy, - self.evm_opts.initial_balance, - self.sender, - &self.revert_decoder, - self.debug, + libs_to_deploy: &self.libs_to_deploy, + executor, + revert_decoder: &self.revert_decoder, + initial_balance: self.evm_opts.initial_balance, + sender: self.sender.unwrap_or_default(), + debug: self.debug, progress, - ); - - let r = runner.run_tests(filter, &self.test_options, self.known_contracts.clone(), handle); + tokio_handle, + span, + }; + let r = runner.run_tests(filter, &self.test_options, self.known_contracts.clone()); debug!(duration=?r.duration, "executed all tests in contract"); @@ -307,6 +323,8 @@ pub struct MultiContractRunnerBuilder { pub coverage: bool, /// Whether or not to collect debug info pub debug: bool, + /// Whether to enable steps tracking in the tracer. + pub decode_internal: InternalTraceMode, /// Whether to enable call isolation pub isolation: bool, /// Settings related to fuzz and/or invariant tests @@ -325,6 +343,7 @@ impl MultiContractRunnerBuilder { debug: Default::default(), isolation: Default::default(), test_options: Default::default(), + decode_internal: Default::default(), } } @@ -363,6 +382,11 @@ impl MultiContractRunnerBuilder { self } + pub fn set_decode_internal(mut self, mode: InternalTraceMode) -> Self { + self.decode_internal = mode; + self + } + pub fn enable_isolation(mut self, enable: bool) -> Self { self.isolation = enable; self @@ -408,7 +432,7 @@ impl MultiContractRunnerBuilder { // if it's a test, link it and add to deployable contracts if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && - abi.functions().any(|func| func.name.is_test() || func.name.is_invariant_test()) + abi.functions().any(|func| func.name.is_any_test()) { let Some(bytecode) = contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty()) @@ -465,6 +489,7 @@ impl MultiContractRunnerBuilder { config: self.config, coverage: self.coverage, debug: self.debug, + decode_internal: self.decode_internal, test_options: self.test_options.unwrap_or_default(), isolation: self.isolation, known_contracts, @@ -483,5 +508,5 @@ pub fn matches_contract(id: &ArtifactId, abi: &JsonAbi, filter: &dyn TestFilter) /// Returns `true` if the function is a test function that matches the given filter. pub(crate) fn is_matching_test(func: &Function, filter: &dyn TestFilter) -> bool { - (func.is_test() || func.is_invariant_test()) && filter.matches_test(&func.signature()) + func.is_any_test() && filter.matches_test(&func.signature()) } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 33db197b2..c7db59f62 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -1,13 +1,16 @@ //! Test outcomes. -use crate::gas_report::GasReport; +use crate::{ + fuzz::{BaseCounterExample, FuzzedCases}, + gas_report::GasReport, +}; use alloy_primitives::{Address, Bytes, Log}; +use eyre::Report; use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; use foundry_evm::{ coverage::HitMaps, - debug::DebugArena, - executors::EvmError, - fuzz::{CounterExample, FuzzCase, FuzzFixtures}, + executors::{EvmError, RawCallResult}, + fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult}, traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; @@ -352,9 +355,6 @@ pub struct TestResult { /// be printed to the user. pub logs: Vec, - /// The decoded DSTest logging events and Hardhat's `console.log` from [logs](Self::logs). - pub decoded_logs: Vec, - /// What kind of test this was pub kind: TestKind, @@ -373,9 +373,6 @@ pub struct TestResult { /// Labeled addresses pub labeled_addresses: HashMap, - /// The debug nodes of the call - pub debug: Option, - pub duration: Duration, /// pc breakpoint char map @@ -416,10 +413,162 @@ impl fmt::Display for TestResult { } impl TestResult { + /// Creates a new test result starting from test setup results. + pub fn new(setup: TestSetup) -> Self { + Self { + labeled_addresses: setup.labeled_addresses, + logs: setup.logs, + traces: setup.traces, + coverage: setup.coverage, + ..Default::default() + } + } + + /// Creates a failed test result with given reason. pub fn fail(reason: String) -> Self { Self { status: TestStatus::Failure, reason: Some(reason), ..Default::default() } } + /// Creates a failed test setup result. + pub fn setup_fail(setup: TestSetup) -> Self { + Self { + status: TestStatus::Failure, + reason: setup.reason, + logs: setup.logs, + traces: setup.traces, + coverage: setup.coverage, + labeled_addresses: setup.labeled_addresses, + ..Default::default() + } + } + + /// Returns the skipped result for single test (used in skipped fuzz test too). + pub fn single_skip(mut self) -> Self { + self.status = TestStatus::Skipped; + self + } + + /// Returns the failed result with reason for single test. + pub fn single_fail(mut self, err: EvmError) -> Self { + self.status = TestStatus::Failure; + self.reason = Some(err.to_string()); + self + } + + /// Returns the result for single test. Merges execution results (logs, labeled addresses, + /// traces and coverages) in initial setup results. + pub fn single_result( + mut self, + success: bool, + reason: Option, + raw_call_result: RawCallResult, + ) -> Self { + self.kind = + TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) }; + + // Record logs, labels, traces and merge coverages. + self.logs.extend(raw_call_result.logs); + self.labeled_addresses.extend(raw_call_result.labels); + self.traces.extend(raw_call_result.traces.map(|traces| (TraceKind::Execution, traces))); + self.merge_coverages(raw_call_result.coverage); + + self.status = match success { + true => TestStatus::Success, + false => TestStatus::Failure, + }; + self.reason = reason; + self.breakpoints = raw_call_result.cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); + self.duration = Duration::default(); + self.gas_report_traces = Vec::new(); + self + } + + /// Returns the result for a fuzzed test. Merges fuzz execution results (logs, labeled + /// addresses, traces and coverages) in initial setup results. + pub fn fuzz_result(mut self, result: FuzzTestResult) -> Self { + self.kind = TestKind::Fuzz { + median_gas: result.median_gas(false), + mean_gas: result.mean_gas(false), + first_case: result.first_case, + runs: result.gas_by_case.len(), + }; + + // Record logs, labels, traces and merge coverages. + self.logs.extend(result.logs); + self.labeled_addresses.extend(result.labeled_addresses); + self.traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); + self.merge_coverages(result.coverage); + + self.status = match result.success { + true => TestStatus::Success, + false => TestStatus::Failure, + }; + self.reason = result.reason; + self.counterexample = result.counterexample; + self.duration = Duration::default(); + self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); + self.breakpoints = result.breakpoints.unwrap_or_default(); + self + } + + /// Returns the skipped result for invariant test. + pub fn invariant_skip(mut self) -> Self { + self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }; + self.status = TestStatus::Skipped; + self + } + + /// Returns the fail result for replayed invariant test. + pub fn invariant_replay_fail( + mut self, + replayed_entirely: bool, + invariant_name: &String, + call_sequence: Vec, + ) -> Self { + self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }; + self.status = TestStatus::Failure; + self.reason = if replayed_entirely { + Some(format!("{invariant_name} replay failure")) + } else { + Some(format!("{invariant_name} persisted failure revert")) + }; + self.counterexample = Some(CounterExample::Sequence(call_sequence)); + self + } + + /// Returns the fail result for invariant test setup. + pub fn invariant_setup_fail(mut self, e: Report) -> Self { + self.kind = TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }; + self.status = TestStatus::Failure; + self.reason = Some(format!("failed to set up invariant testing environment: {e}")); + self + } + + /// Returns the invariant test result. + pub fn invariant_result( + mut self, + gas_report_traces: Vec>, + success: bool, + reason: Option, + counterexample: Option, + cases: Vec, + reverts: usize, + ) -> Self { + self.kind = TestKind::Invariant { + runs: cases.len(), + calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), + reverts, + }; + self.status = match success { + true => TestStatus::Success, + false => TestStatus::Failure, + }; + self.reason = reason; + self.counterexample = counterexample; + self.gas_report_traces = gas_report_traces; + self + } + /// Returns `true` if this is the result of a fuzz test pub fn is_fuzz(&self) -> bool { matches!(self.kind, TestKind::Fuzz { .. }) @@ -429,6 +578,15 @@ impl TestResult { pub fn short_result(&self, name: &str) -> String { format!("{self} {name} {}", self.kind.report()) } + + /// Function to merge given coverage in current test result coverage. + pub fn merge_coverages(&mut self, other_coverage: Option) { + let old_coverage = std::mem::take(&mut self.coverage); + self.coverage = match (old_coverage, other_coverage) { + (Some(old_coverage), Some(other)) => Some(old_coverage.merged(other)), + (a, b) => a.or(b), + }; + } } /// Data report by a test. diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 59b0943a6..55fb9b653 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -4,7 +4,7 @@ use crate::{ fuzz::{invariant::BasicTxDetails, BaseCounterExample}, multi_runner::{is_matching_test, TestContract}, progress::{start_fuzz_progress, TestsProgress}, - result::{SuiteResult, TestKind, TestResult, TestSetup, TestStatus}, + result::{SuiteResult, TestResult, TestSetup}, TestFilter, TestOptions, }; use alloy_dyn_abi::DynSolValue; @@ -13,18 +13,16 @@ use alloy_primitives::{address, Address, Bytes, U256}; use eyre::Result; use foundry_common::{ contracts::{ContractsByAddress, ContractsByArtifact}, - TestFunctionExt, + TestFunctionExt, TestFunctionKind, }; use foundry_config::{FuzzConfig, InvariantConfig}; use foundry_evm::{ constants::CALLER, - coverage::HitMaps, - decode::{decode_console_logs, RevertDecoder}, + decode::RevertDecoder, executors::{ - fuzz::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzedExecutor}, + fuzz::FuzzedExecutor, invariant::{ check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError, - InvariantFuzzTestResult, }, CallResult, EvmError, ExecutionErr, Executor, RawCallResult, }, @@ -33,7 +31,7 @@ use foundry_evm::{ invariant::{CallDetails, InvariantContract}, CounterExample, FuzzFixtures, }, - traces::{load_contracts, TraceKind}, + traces::{load_contracts, TraceKind, TraceMode}, }; use proptest::test_runner::TestRunner; use rayon::prelude::*; @@ -53,8 +51,9 @@ pub const LIBRARY_DEPLOYER: Address = address!("1F95D37F27EA0dEA9C252FC09D5A6eaA /// A type that executes all tests of a contract #[derive(Clone, Debug)] pub struct ContractRunner<'a> { + /// The name of the contract. pub name: &'a str, - /// The data of the contract being ran. + /// The data of the contract. pub contract: &'a TestContract, /// The libraries that need to be deployed before the contract. pub libs_to_deploy: &'a Vec, @@ -62,41 +61,18 @@ pub struct ContractRunner<'a> { pub executor: Executor, /// Revert decoder. Contains all known errors. pub revert_decoder: &'a RevertDecoder, - /// The initial balance of the test contract + /// The initial balance of the test contract. pub initial_balance: U256, - /// The address which will be used as the `from` field in all EVM calls + /// The address which will be used as the `from` field in all EVM calls. pub sender: Address, - /// Should generate debug traces + /// Whether debug traces should be generated. pub debug: bool, /// Overall test run progress. - progress: Option<&'a TestsProgress>, -} - -impl<'a> ContractRunner<'a> { - #[allow(clippy::too_many_arguments)] - pub fn new( - name: &'a str, - executor: Executor, - contract: &'a TestContract, - libs_to_deploy: &'a Vec, - initial_balance: U256, - sender: Option
, - revert_decoder: &'a RevertDecoder, - debug: bool, - progress: Option<&'a TestsProgress>, - ) -> Self { - Self { - name, - executor, - contract, - libs_to_deploy, - initial_balance, - sender: sender.unwrap_or_default(), - revert_decoder, - debug, - progress, - } - } + pub progress: Option<&'a TestsProgress>, + /// The handle to the tokio runtime. + pub tokio_handle: &'a tokio::runtime::Handle, + /// The span of the contract. + pub span: tracing::Span, } impl<'a> ContractRunner<'a> { @@ -282,9 +258,7 @@ impl<'a> ContractRunner<'a> { filter: &dyn TestFilter, test_options: &TestOptions, known_contracts: ContractsByArtifact, - handle: &tokio::runtime::Handle, ) -> SuiteResult { - info!("starting tests"); let start = Instant::now(); let mut warnings = Vec::new(); @@ -338,36 +312,25 @@ impl<'a> ContractRunner<'a> { }); // Invariant testing requires tracing to figure out what contracts were created. + // We also want to disable `debug` for setup since we won't be using those traces. let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test()); - let tmp_tracing = self.executor.inspector.tracer.is_none() && has_invariants && call_setup; - if tmp_tracing { - self.executor.set_tracing(true); + + let prev_tracer = self.executor.inspector_mut().tracer.take(); + if prev_tracer.is_some() || has_invariants { + self.executor.set_tracing(TraceMode::Call); } + let setup_time = Instant::now(); let setup = self.setup(call_setup); debug!("finished setting up in {:?}", setup_time.elapsed()); - if tmp_tracing { - self.executor.set_tracing(false); - } + + self.executor.inspector_mut().tracer = prev_tracer; if setup.reason.is_some() { // The setup failed, so we return a single test result for `setUp` return SuiteResult::new( start.elapsed(), - [( - "setUp()".to_string(), - TestResult { - status: TestStatus::Failure, - reason: setup.reason, - decoded_logs: decode_console_logs(&setup.logs), - logs: setup.logs, - traces: setup.traces, - coverage: setup.coverage, - labeled_addresses: setup.labeled_addresses, - ..Default::default() - }, - )] - .into(), + [("setUp()".to_string(), TestResult::setup_fail(setup))].into(), warnings, ) } @@ -401,43 +364,50 @@ impl<'a> ContractRunner<'a> { .map(|&func| { let start = Instant::now(); - let _guard = handle.enter(); + let _guard = self.tokio_handle.enter(); - let sig = func.signature(); - let span = debug_span!("test", name = tracing::field::Empty).entered(); - if !span.is_disabled() { - if enabled!(tracing::Level::TRACE) { - span.record("name", &sig); - } else { - span.record("name", &func.name); - } + let _guard; + let current_span = tracing::Span::current(); + if current_span.is_none() || current_span.id() != self.span.id() { + _guard = self.span.enter(); } + let sig = func.signature(); + let kind = func.test_function_kind(); + + let _guard = debug_span!( + "test", + %kind, + name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name }, + ) + .entered(); + let setup = setup.clone(); - let should_fail = func.is_test_fail(); - let mut res = if func.is_invariant_test() { - let runner = test_options.invariant_runner(self.name, &func.name); - let invariant_config = test_options.invariant_config(self.name, &func.name); - - self.run_invariant_test( - runner, - setup, - invariant_config.clone(), - func, - call_after_invariant, - &known_contracts, - identified_contracts.as_ref().unwrap(), - ) - } else if func.is_fuzz_test() { - debug_assert!(func.is_test()); - let runner = test_options.fuzz_runner(self.name, &func.name); - let fuzz_config = test_options.fuzz_config(self.name, &func.name); - info!(name = func.name, "run fuzz test"); - self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config.clone()) - } else { - debug_assert!(func.is_test()); - info!(name = func.name, "run test"); - self.run_test(func, should_fail, setup) + let mut res = match kind { + TestFunctionKind::UnitTest { should_fail } => { + self.run_unit_test(func, should_fail, setup) + } + TestFunctionKind::FuzzTest { should_fail } => { + let runner = test_options.fuzz_runner(self.name, &func.name); + let fuzz_config = test_options.fuzz_config(self.name, &func.name); + + self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config.clone()) + } + TestFunctionKind::InvariantTest => { + let runner = test_options.invariant_runner(self.name, &func.name); + let invariant_config = test_options.invariant_config(self.name, &func.name); + + self.run_invariant_test( + runner, + setup, + invariant_config.clone(), + func, + call_after_invariant, + &known_contracts, + identified_contracts.as_ref().unwrap(), + ) + } + _ => unreachable!(), }; res.duration = start.elapsed(); @@ -447,27 +417,23 @@ impl<'a> ContractRunner<'a> { .collect::>(); let duration = start.elapsed(); - let suite_result = SuiteResult::new(duration, test_results, warnings); - info!( - duration=?suite_result.duration, - "done. {}/{} successful", - suite_result.passed(), - suite_result.test_results.len() - ); - suite_result + SuiteResult::new(duration, test_results, warnings) } - /// Runs a single test + /// Runs a single unit test. /// /// Calls the given functions and returns the `TestResult`. /// /// State modifications are not committed to the evm database but discarded after the call, /// similar to `eth_call`. - #[instrument(level = "debug", name = "normal", skip_all)] - pub fn run_test(&self, func: &Function, should_fail: bool, setup: TestSetup) -> TestResult { - let TestSetup { - address, mut logs, mut traces, mut labeled_addresses, mut coverage, .. - } = setup; + pub fn run_unit_test( + &self, + func: &Function, + should_fail: bool, + setup: TestSetup, + ) -> TestResult { + let address = setup.address; + let test_result = TestResult::new(setup); // Run unit test let (mut raw_call_result, reason) = match self.executor.call( @@ -480,71 +446,15 @@ impl<'a> ContractRunner<'a> { ) { Ok(res) => (res.raw, None), Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), - Err(EvmError::SkipError) => { - return TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - ..Default::default() - } - } - Err(err) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(err.to_string()), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - ..Default::default() - } - } + Err(EvmError::SkipError) => return test_result.single_skip(), + Err(err) => return test_result.single_fail(err), }; let success = - self.executor.is_raw_call_mut_success(setup.address, &mut raw_call_result, should_fail); - - let RawCallResult { - gas_used: gas, - stipend, - logs: execution_logs, - traces: execution_trace, - coverage: execution_coverage, - labels: new_labels, - debug, - cheatcodes, - .. - } = raw_call_result; - - let breakpoints = cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); - let debug_arena = debug; - traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(new_labels); - logs.extend(execution_logs); - coverage = merge_coverages(coverage, execution_coverage); - - TestResult { - status: match success { - true => TestStatus::Success, - false => TestStatus::Failure, - }, - reason, - counterexample: None, - decoded_logs: decode_console_logs(&logs), - logs, - kind: TestKind::Unit { gas: gas.wrapping_sub(stipend) }, - traces, - coverage, - labeled_addresses, - debug: debug_arena, - breakpoints, - duration: std::time::Duration::default(), - gas_report_traces: Vec::new(), - } + self.executor.is_raw_call_mut_success(address, &mut raw_call_result, should_fail); + test_result.single_result(success, reason, raw_call_result) } - #[instrument(level = "debug", name = "invariant", skip_all)] #[allow(clippy::too_many_arguments)] pub fn run_invariant_test( &self, @@ -556,8 +466,9 @@ impl<'a> ContractRunner<'a> { known_contracts: &ContractsByArtifact, identified_contracts: &ContractsByAddress, ) -> TestResult { - let TestSetup { address, logs, traces, labeled_addresses, coverage, fuzz_fixtures, .. } = - setup; + let address = setup.address; + let fuzz_fixtures = setup.fuzz_fixtures.clone(); + let mut test_result = TestResult::new(setup); // First, run the test normally to see if it needs to be skipped. if let Err(EvmError::SkipError) = self.executor.call( @@ -568,16 +479,7 @@ impl<'a> ContractRunner<'a> { U256::ZERO, Some(self.revert_decoder), ) { - return TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, - coverage, - ..Default::default() - } + return test_result.invariant_skip() }; let mut evm = InvariantExecutor::new( @@ -594,10 +496,6 @@ impl<'a> ContractRunner<'a> { abi: &self.contract.abi, }; - let mut logs = logs.clone(); - let mut traces = traces.clone(); - let mut coverage = coverage.clone(); - let failure_dir = invariant_config.clone().failure_dir(self.name); let failure_file = failure_dir.join(invariant_contract.invariant_function.clone().name); @@ -633,61 +531,36 @@ impl<'a> ContractRunner<'a> { self.executor.clone(), known_contracts, identified_contracts.clone(), - &mut logs, - &mut traces, - &mut coverage, + &mut test_result.logs, + &mut test_result.traces, + &mut test_result.coverage, &txes, ); - return TestResult { - status: TestStatus::Failure, - reason: if replayed_entirely { - Some(format!( - "{} replay failure", - invariant_contract.invariant_function.name - )) - } else { - Some(format!( - "{} persisted failure revert", - invariant_contract.invariant_function.name - )) - }, - decoded_logs: decode_console_logs(&logs), - traces, - coverage, - counterexample: Some(CounterExample::Sequence(call_sequence)), - kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, - ..Default::default() - } + return test_result.invariant_replay_fail( + replayed_entirely, + &invariant_contract.invariant_function.name, + call_sequence, + ) } } } let progress = start_fuzz_progress(self.progress, self.name, &func.name, invariant_config.runs); - let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs, gas_report_traces } = + let invariant_result = match evm.invariant_fuzz(invariant_contract.clone(), &fuzz_fixtures, progress.as_ref()) { Ok(x) => x, - Err(e) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(format!( - "failed to set up invariant testing environment: {e}" - )), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, - ..Default::default() - } - } + Err(e) => return test_result.invariant_setup_fail(e), }; + // Merge coverage collected during invariant run with test setup coverage. + test_result.merge_coverages(invariant_result.coverage); let mut counterexample = None; - let success = error.is_none(); - let reason = error.as_ref().and_then(|err| err.revert_reason()); + let success = invariant_result.error.is_none(); + let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason()); - match error { + match invariant_result.error { // If invariants were broken, replay the error to collect logs and traces Some(error) => match error { InvariantFuzzError::BrokenInvariant(case_data) | @@ -700,9 +573,9 @@ impl<'a> ContractRunner<'a> { self.executor.clone(), known_contracts, identified_contracts.clone(), - &mut logs, - &mut traces, - &mut coverage, + &mut test_result.logs, + &mut test_result.traces, + &mut test_result.coverage, progress.as_ref(), ) { Ok(call_sequence) => { @@ -735,39 +608,26 @@ impl<'a> ContractRunner<'a> { self.executor.clone(), known_contracts, identified_contracts.clone(), - &mut logs, - &mut traces, - &mut coverage, - &last_run_inputs, + &mut test_result.logs, + &mut test_result.traces, + &mut test_result.coverage, + &invariant_result.last_run_inputs, ) { error!(%err, "Failed to replay last invariant run"); } } } - TestResult { - status: match success { - true => TestStatus::Success, - false => TestStatus::Failure, - }, + test_result.invariant_result( + invariant_result.gas_report_traces, + success, reason, counterexample, - decoded_logs: decode_console_logs(&logs), - logs, - kind: TestKind::Invariant { - runs: cases.len(), - calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), - reverts, - }, - coverage, - traces, - labeled_addresses: labeled_addresses.clone(), - gas_report_traces, - ..Default::default() // TODO collect debug traces on the last run or error - } + invariant_result.cases, + invariant_result.reverts, + ) } - #[instrument(level = "debug", name = "fuzz", skip_all)] pub fn run_fuzz_test( &self, func: &Function, @@ -776,24 +636,14 @@ impl<'a> ContractRunner<'a> { setup: TestSetup, fuzz_config: FuzzConfig, ) -> TestResult { - let TestSetup { - address, - mut logs, - mut traces, - mut labeled_addresses, - mut coverage, - fuzz_fixtures, - .. - } = setup; + let address = setup.address; + let fuzz_fixtures = setup.fuzz_fixtures.clone(); + let test_result = TestResult::new(setup); // Run fuzz test let progress = start_fuzz_progress(self.progress, self.name, &func.name, fuzz_config.runs); - let fuzzed_executor = FuzzedExecutor::new( - self.executor.clone(), - runner.clone(), - self.sender, - fuzz_config.clone(), - ); + let fuzzed_executor = + FuzzedExecutor::new(self.executor.clone(), runner, self.sender, fuzz_config); let result = fuzzed_executor.fuzz( func, &fuzz_fixtures, @@ -803,102 +653,11 @@ impl<'a> ContractRunner<'a> { progress.as_ref(), ); - let mut debug = Default::default(); - let mut breakpoints = Default::default(); - // Check the last test result and skip the test // if it's marked as so. if let Some("SKIPPED") = result.reason.as_deref() { - return TestResult { - status: TestStatus::Skipped, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - debug, - breakpoints, - coverage, - ..Default::default() - } - } - - // if should debug - if self.debug { - let mut debug_executor = self.executor.clone(); - // turn the debug traces on - debug_executor.inspector.enable_debugger(true); - debug_executor.inspector.tracing(true); - let calldata = if let Some(counterexample) = result.counterexample.as_ref() { - match counterexample { - CounterExample::Single(ce) => ce.calldata.clone(), - _ => unimplemented!(), - } - } else { - result.first_case.calldata.clone() - }; - // rerun the last relevant test with traces - let debug_result = - FuzzedExecutor::new(debug_executor, runner, self.sender, fuzz_config).single_fuzz( - address, - should_fail, - calldata, - ); - - (debug, breakpoints) = match debug_result { - Ok(fuzz_outcome) => match fuzz_outcome { - FuzzOutcome::Case(CaseOutcome { debug, breakpoints, .. }) => { - (debug, breakpoints) - } - FuzzOutcome::CounterExample(CounterExampleOutcome { - debug, - breakpoints, - .. - }) => (debug, breakpoints), - }, - Err(_) => (Default::default(), Default::default()), - }; + return test_result.single_skip() } - - let kind = TestKind::Fuzz { - median_gas: result.median_gas(false), - mean_gas: result.mean_gas(false), - first_case: result.first_case, - runs: result.gas_by_case.len(), - }; - - // Record logs, labels and traces - logs.extend(result.logs); - labeled_addresses.extend(result.labeled_addresses); - traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); - coverage = merge_coverages(coverage, result.coverage); - - TestResult { - status: match result.success { - true => TestStatus::Success, - false => TestStatus::Failure, - }, - reason: result.reason, - counterexample: result.counterexample, - decoded_logs: decode_console_logs(&logs), - logs, - kind, - traces, - coverage, - labeled_addresses, - debug, - breakpoints, - duration: std::time::Duration::default(), - gas_report_traces: result.gas_report_traces.into_iter().map(|t| vec![t]).collect(), - } - } -} - -/// Utility function to merge coverage options -fn merge_coverages(mut coverage: Option, other: Option) -> Option { - let old_coverage = std::mem::take(&mut coverage); - match (old_coverage, other) { - (Some(old_coverage), Some(other)) => Some(old_coverage.merged(other)), - (None, Some(other)) => Some(other), - (Some(old_coverage), None) => Some(old_coverage), - (None, None) => None, + test_result.fuzz_result(result) } } diff --git a/crates/forge/tests/cli/bind_json.rs b/crates/forge/tests/cli/bind_json.rs new file mode 100644 index 000000000..bdc8f0fa1 --- /dev/null +++ b/crates/forge/tests/cli/bind_json.rs @@ -0,0 +1,54 @@ +// tests complete bind-json workflow +// ensures that we can run forge-bind even if files are depending on yet non-existent bindings and +// that generated bindings are correct +forgetest_init!(test_bind_json, |prj, cmd| { + prj.add_test( + "JsonBindings", + r#" +import {JsonBindings} from "utils/JsonBindings.sol"; +import {Test} from "forge-std/Test.sol"; + +struct TopLevelStruct { + uint256 param1; + int8 param2; +} + +contract BindJsonTest is Test { + using JsonBindings for *; + + struct ContractLevelStruct { + address[][] param1; + address addrParam; + } + + function testTopLevel() public { + string memory json = '{"param1": 1, "param2": -1}'; + TopLevelStruct memory topLevel = json.deserializeTopLevelStruct(); + assertEq(topLevel.param1, 1); + assertEq(topLevel.param2, -1); + + json = topLevel.serialize(); + TopLevelStruct memory deserialized = json.deserializeTopLevelStruct(); + assertEq(keccak256(abi.encode(deserialized)), keccak256(abi.encode(topLevel))); + } + + function testContractLevel() public { + ContractLevelStruct memory contractLevel = ContractLevelStruct({ + param1: new address[][](2), + addrParam: address(0xBEEF) + }); + + string memory json = contractLevel.serialize(); + assertEq(json, '{"param1":[[],[]],"addrParam":"0x000000000000000000000000000000000000bEEF"}'); + + ContractLevelStruct memory deserialized = json.deserializeContractLevelStruct(); + assertEq(keccak256(abi.encode(deserialized)), keccak256(abi.encode(contractLevel))); + } +} +"#, + ) + .unwrap(); + + cmd.arg("bind-json").assert_success(); + cmd.forge_fuse().args(["test"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index c4068d04e..96929a9fc 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,9 +1,7 @@ -use foundry_common::fs::read_json_file; use foundry_config::Config; use foundry_test_utils::forgetest; use globset::Glob; use regex::Regex; -use std::{collections::BTreeMap, path::PathBuf}; // tests that json is printed when --json is passed forgetest!(compile_json, |prj, cmd| { @@ -21,26 +19,14 @@ contract Dummy { .unwrap(); // set up command - cmd.args(["compile", "--format-json"]); - - // Exclude build_infos from output as IDs depend on root dir and are not deterministic. - let mut output: BTreeMap = - serde_json::from_str(&cmd.stdout_lossy()).unwrap(); - output.remove("build_infos"); - - let expected: BTreeMap = read_json_file( - &PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"), - ) - .unwrap(); - - similar_asserts::assert_eq!(output, expected); + cmd.args(["compile", "--format-json"]) + .assert() + .stdout_eq(file!["../fixtures/compile_json.stdout": Json]); }); // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { - cmd.args(["build", "--force"]); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("Compiling"), "\n{stdout}"); + cmd.args(["build", "--force"]).assert_success().stdout_eq(str!["Compiling[..]\n..."]); }); // tests build output is as expected diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 65f5f6ae8..23cb3ca8a 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1,6 +1,7 @@ //! Contains various tests for checking forge's commands use crate::constants::*; +use alloy_primitives::hex; use foundry_compilers::artifacts::{remappings::Remapping, ConfigurableContractArtifact, Metadata}; use foundry_config::{ parse_with_profile, BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, @@ -506,7 +507,21 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", ]) .arg(prj.root()); - cmd.assert_non_empty_stdout(); + let out = cmd.unchecked_output(); + if out.stdout_lossy().contains("502 Bad Gateway") { + // etherscan nginx proxy issue, skip this test: + // + // stdout: + // Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from + // Etherscan... 2024-07-05T11:40:11.801765Z ERROR etherscan: Failed to deserialize + // response: expected value at line 1 column 1 res="\r\n502 Bad + // Gateway\r\n\r\n

502 Bad + // Gateway

\r\n
nginx
\r\n\r\n\r\n" + + eprintln!("Skipping test due to 502 Bad Gateway: {}", cmd.make_error_message(&out, false)); + return + } + cmd.ensure_success(&out).unwrap(); let s = read_string(&foundry_toml); let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; @@ -576,7 +591,7 @@ forgetest_init!(can_emit_extra_output, |prj, cmd| { let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON); let artifact: ConfigurableContractArtifact = - foundry_compilers::utils::read_json_file(artifact_path).unwrap(); + foundry_compilers::utils::read_json_file(&artifact_path).unwrap(); assert!(artifact.metadata.is_some()); cmd.forge_fuse().args(["build", "--extra-output-files", "metadata", "--force"]).root_arg(); @@ -584,7 +599,7 @@ forgetest_init!(can_emit_extra_output, |prj, cmd| { let metadata_path = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json")); - let _artifact: Metadata = foundry_compilers::utils::read_json_file(metadata_path).unwrap(); + let _artifact: Metadata = foundry_compilers::utils::read_json_file(&metadata_path).unwrap(); }); // checks that extra output works @@ -594,7 +609,7 @@ forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| { let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON); let artifact: ConfigurableContractArtifact = - foundry_compilers::utils::read_json_file(artifact_path).unwrap(); + foundry_compilers::utils::read_json_file(&artifact_path).unwrap(); assert!(artifact.metadata.is_some()); assert!(artifact.ir.is_some()); assert!(artifact.ir_optimized.is_some()); @@ -613,7 +628,7 @@ forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| { let metadata_path = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json")); - let _artifact: Metadata = foundry_compilers::utils::read_json_file(metadata_path).unwrap(); + let _artifact: Metadata = foundry_compilers::utils::read_json_file(&metadata_path).unwrap(); let iropt = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.iropt")); std::fs::read_to_string(iropt).unwrap(); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index f5d47c624..cd0e04c25 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -64,12 +64,17 @@ forgetest!(can_extract_config_values, |prj, cmd| { contract_pattern_inverse: None, path_pattern: None, path_pattern_inverse: None, + coverage_pattern_inverse: None, + test_failures_file: "test-cache/test-failures".into(), + threads: None, + show_progress: false, fuzz: FuzzConfig { runs: 1000, max_test_rejects: 100203, seed: Some(U256::from(1000)), failure_persist_dir: Some("test-cache/fuzz".into()), failure_persist_file: Some("failures".to_string()), + show_logs: false, ..Default::default() }, invariant: InvariantConfig { @@ -102,7 +107,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { etherscan_api_key: None, etherscan: Default::default(), verbosity: 4, - remappings: vec![Remapping::from_str("forge-std=lib/forge-std/").unwrap().into()], + remappings: vec![Remapping::from_str("forge-std/=lib/forge-std/").unwrap().into()], libraries: vec![ "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6".to_string() ], @@ -129,6 +134,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { build_info_path: None, fmt: Default::default(), doc: Default::default(), + bind_json: Default::default(), fs_permissions: Default::default(), labels: Default::default(), prague: true, @@ -139,6 +145,10 @@ forgetest!(can_extract_config_values, |prj, cmd| { skip: vec![], dependencies: Default::default(), warnings: vec![], + assertions_revert: true, + legacy_assertions: false, + extra_args: vec![], + eof_version: None, _non_exhaustive: (), zksync: Default::default(), }; diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 546a11038..d33cbb47f 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -1,4 +1,4 @@ -use regex::Regex; +use foundry_test_utils::{assert_data_eq, str}; forgetest!(basic_coverage, |_prj, cmd| { cmd.args(["coverage"]); @@ -57,6 +57,258 @@ contract AContractTest is DSTest { ) .unwrap(); + // Assert 100% coverage (init function coverage called in setUp is accounted). + cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | + +"#]]); +}); + +forgetest!(test_no_match_coverage, |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(); + + prj.add_source( + "BContract.sol", + r#" +contract BContract { + int public i; + + function init() public { + i = 0; + } + + function foo() public { + i = 1; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "BContractTest.sol", + r#" +import "./test.sol"; +import {BContract} from "./BContract.sol"; + +contract BContractTest is DSTest { + BContract a; + + function setUp() public { + a = new BContract(); + a.init(); + } + + function testFoo() public { + a.foo(); + } +} + "#, + ) + .unwrap(); + + // Assert AContract is not included in report. + cmd.arg("coverage") + .args([ + "--no-match-coverage".to_string(), + "AContract".to_string(), // Filter out `AContract` + ]) + .assert_success() + .stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/BContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | + +"#]]); +}); + +forgetest!(test_assert_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + function checkA() external pure returns (bool) { + assert(10 > 2); + return true; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + function testA() external { + AContract a = new AContract(); + bool result = a.checkA(); + assertTrue(result); + } +} + "#, + ) + .unwrap(); + + // Assert 100% coverage (assert properly covered). + cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | + +"#]]); +}); + +forgetest!(test_require_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + function checkRequire(bool doNotRevert) public view { + require(doNotRevert, "reverted"); + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +interface Vm { + function expectRevert(bytes calldata revertData) external; +} + +contract AContractTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + function testRequireRevert() external { + AContract a = new AContract(); + vm.expectRevert(abi.encodePacked("reverted")); + a.checkRequire(false); + } + + function testRequireNoRevert() external { + AContract a = new AContract(); + a.checkRequire(true); + } +} + "#, + ) + .unwrap(); + + // Assert 50% branch coverage if only revert tested. + cmd.arg("coverage") + .args(["--mt".to_string(), "testRequireRevert".to_string()]) + .assert_success() + .stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|--------------|---------------| +| src/AContract.sol | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | + +"#]]); + + // Assert 100% branch coverage. + cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( + str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (1/1) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +| Total | 100.00% (1/1) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | + +"#]], + ); +}); + +forgetest!(test_line_hit_not_doubled, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + int public i; + + function foo() public { + i = 1; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + function testFoo() public { + AContract a = new AContract(); + a.foo(); + } +} + "#, + ) + .unwrap(); + let lcov_info = prj.root().join("lcov.info"); cmd.arg("coverage").args([ "--report".to_string(), @@ -67,12 +319,582 @@ contract AContractTest is DSTest { cmd.assert_success(); assert!(lcov_info.exists()); - let lcov_data = std::fs::read_to_string(lcov_info).unwrap(); - // AContract.init must be hit at least once - let re = Regex::new(r"FNDA:(\d+),AContract\.init").unwrap(); - let valid_line = |line| { - re.captures(line) - .map_or(false, |caps| caps.get(1).unwrap().as_str().parse::().unwrap() > 0) - }; - assert!(lcov_data.lines().any(valid_line), "{lcov_data}"); + // We want to make sure DA:8,1 is added only once so line hit is not doubled. + assert_data_eq!( + std::fs::read_to_string(lcov_info).unwrap(), + str![[r#"TN: +SF:src/AContract.sol +FN:7,AContract.foo +FNDA:1,AContract.foo +DA:8,1 +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end[..] +"#]] + ); +}); + +forgetest!(test_branch_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Foo.sol", + r#" +contract Foo { + error Gte1(uint256 number, uint256 firstElement); + + enum Status { + NULL, + OPEN, + CLOSED + } + + struct Item { + Status status; + uint256 value; + } + + mapping(uint256 => Item) internal items; + uint256 public nextId = 1; + + function getItem(uint256 id) public view returns (Item memory item) { + item = items[id]; + } + + function addItem(uint256 value) public returns (uint256 id) { + id = nextId; + items[id] = Item(Status.OPEN, value); + nextId++; + } + + function closeIfEqValue(uint256 id, uint256 value) public { + if (items[id].value == value) { + items[id].status = Status.CLOSED; + } + } + + function incrementIfEqValue(uint256 id, uint256 value) public { + if (items[id].value == value) { + items[id].value = value + 1; + } + } + + function foo(uint256 a) external pure { + if (a < 10) { + if (a == 1) { + assert(a == 1); + } else { + assert(a == 5); + } + } else { + assert(a == 60); + } + } + + function countOdd(uint256[] memory arr) external pure returns (uint256 count) { + uint256 length = arr.length; + for (uint256 i = 0; i < length; ++i) { + if (arr[i] % 2 == 1) { + count++; + arr[0]; + } + } + } + + function checkLt(uint256 number, uint256[] memory arr) external pure returns (bool) { + if (number >= arr[0]) { + revert Gte1(number, arr[0]); + } + return true; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FooTest.sol", + r#" +import "./test.sol"; +import {Foo} from "./Foo.sol"; + +interface Vm { + function expectRevert(bytes calldata revertData) external; +} + +contract FooTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Foo internal foo = new Foo(); + + function test_issue_7784() external view { + foo.foo(1); + foo.foo(5); + foo.foo(60); + } + + function test_issue_4310() external { + uint256[] memory arr = new uint256[](3); + arr[0] = 78; + arr[1] = 493; + arr[2] = 700; + uint256 count = foo.countOdd(arr); + assertEq(count, 1); + + arr = new uint256[](4); + arr[0] = 78; + arr[1] = 493; + arr[2] = 700; + arr[3] = 1729; + count = foo.countOdd(arr); + assertEq(count, 2); + } + + function test_issue_4315() external { + uint256 value = 42; + uint256 id = foo.addItem(value); + assertEq(id, 1); + assertEq(foo.nextId(), 2); + Foo.Item memory item = foo.getItem(id); + assertEq(uint8(item.status), uint8(Foo.Status.OPEN)); + assertEq(item.value, value); + + foo = new Foo(); + id = foo.addItem(value); + foo.closeIfEqValue(id, 903); + item = foo.getItem(id); + assertEq(uint8(item.status), uint8(Foo.Status.OPEN)); + + foo = new Foo(); + foo.addItem(value); + foo.closeIfEqValue(id, 42); + item = foo.getItem(id); + assertEq(uint8(item.status), uint8(Foo.Status.CLOSED)); + + foo = new Foo(); + id = foo.addItem(value); + foo.incrementIfEqValue(id, 903); + item = foo.getItem(id); + assertEq(item.value, 42); + + foo = new Foo(); + id = foo.addItem(value); + foo.incrementIfEqValue(id, 42); + item = foo.getItem(id); + assertEq(item.value, 43); + } + + function test_issue_4309() external { + uint256[] memory arr = new uint256[](1); + arr[0] = 1; + uint256 number = 2; + vm.expectRevert(abi.encodeWithSelector(Foo.Gte1.selector, number, arr[0])); + foo.checkLt(number, arr); + + number = 1; + vm.expectRevert(abi.encodeWithSelector(Foo.Gte1.selector, number, arr[0])); + foo.checkLt(number, arr); + + number = 0; + bool result = foo.checkLt(number, arr); + assertTrue(result); + } +} + "#, + ) + .unwrap(); + + // Assert 100% coverage. + cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|-----------------|-----------------|-----------------|---------------| +| src/Foo.sol | 100.00% (20/20) | 100.00% (23/23) | 100.00% (12/12) | 100.00% (7/7) | +| Total | 100.00% (20/20) | 100.00% (23/23) | 100.00% (12/12) | 100.00% (7/7) | + +"#]]); +}); + +forgetest!(test_function_call_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + struct Custom { + bool a; + uint256 b; + } + + function coverMe() external returns (bool) { + // Next lines should not be counted in coverage. + string(""); + uint256(1); + address(this); + bool(false); + Custom(true, 10); + // Next lines should be counted in coverage. + uint256 a = uint256(1); + Custom memory cust = Custom(false, 100); + privateWithNoBody(); + privateWithBody(); + publicWithNoBody(); + publicWithBody(); + return true; + } + + function privateWithNoBody() private {} + + function privateWithBody() private returns (bool) { + return true; + } + + function publicWithNoBody() private {} + + function publicWithBody() private returns (bool) { + return true; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + function testTypeConversionCoverage() external { + AContract a = new AContract(); + a.coverMe(); + } +} + "#, + ) + .unwrap(); + + // Assert 100% coverage and only 9 lines reported (comments, type conversions and struct + // constructor calls are not included). + cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|---------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +| Total | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | + +"#]]); +}); + +forgetest!(test_try_catch_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Foo.sol", + r#" +contract Foo { + address public owner; + + constructor(address _owner) { + require(_owner != address(0), "invalid address"); + assert(_owner != 0x0000000000000000000000000000000000000001); + owner = _owner; + } + + function myFunc(uint256 x) public pure returns (string memory) { + require(x != 0, "require failed"); + return "my func was called"; + } +} + +contract Bar { + event Log(string message); + event LogBytes(bytes data); + + Foo public foo; + + constructor() { + foo = new Foo(msg.sender); + } + + function tryCatchExternalCall(uint256 _i) public { + try foo.myFunc(_i) returns (string memory result) { + emit Log(result); + } catch { + emit Log("external call failed"); + } + } + + function tryCatchNewContract(address _owner) public { + try new Foo(_owner) returns (Foo foo_) { + emit Log("Foo created"); + } catch Error(string memory reason) { + emit Log(reason); + } catch (bytes memory reason) {} + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FooTest.sol", + r#" +import "./test.sol"; +import {Bar, Foo} from "./Foo.sol"; + +interface Vm { + function expectRevert() external; +} + +contract FooTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_happy_foo_coverage() external { + vm.expectRevert(); + Foo foo = new Foo(address(0)); + vm.expectRevert(); + foo = new Foo(address(1)); + foo = new Foo(address(2)); + } + + function test_happy_path_coverage() external { + Bar bar = new Bar(); + bar.tryCatchNewContract(0x0000000000000000000000000000000000000002); + bar.tryCatchExternalCall(1); + } + + function test_coverage() external { + Bar bar = new Bar(); + bar.tryCatchNewContract(0x0000000000000000000000000000000000000000); + bar.tryCatchNewContract(0x0000000000000000000000000000000000000001); + bar.tryCatchExternalCall(0); + } +} + "#, + ) + .unwrap(); + + // Assert coverage not 100% for happy paths only. + cmd.arg("coverage").args(["--mt".to_string(), "happy".to_string()]).assert_success().stdout_eq( + str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|----------------|----------------|---------------|---------------| +| src/Foo.sol | 66.67% (10/15) | 66.67% (14/21) | 100.00% (4/4) | 100.00% (5/5) | +| Total | 66.67% (10/15) | 66.67% (14/21) | 100.00% (4/4) | 100.00% (5/5) | + +"#]], + ); + + // Assert 100% branch coverage (including clauses without body). + cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( + str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|-----------------|-----------------|---------------|---------------| +| src/Foo.sol | 100.00% (15/15) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +| Total | 100.00% (15/15) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | + +"#]], + ); +}); + +forgetest!(test_yul_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Foo.sol", + r#" +contract Foo { + uint256[] dynamicArray; + + function readDynamicArrayLength() public view returns (uint256 length) { + assembly { + length := sload(dynamicArray.slot) + } + } + + function switchAndIfStatements(uint256 n) public pure { + uint256 y; + assembly { + switch n + case 0 { y := 0 } + case 1 { y := 1 } + default { y := n } + + if y { y := 2 } + } + } + + function yulForLoop(uint256 n) public { + uint256 y; + assembly { + for { let i := 0 } lt(i, n) { i := add(i, 1) } { y := add(y, 1) } + + let j := 0 + for {} lt(j, n) { j := add(j, 1) } { j := add(j, 2) } + } + } + + function hello() public pure returns (bool, uint256, bytes32) { + bool x; + uint256 y; + bytes32 z; + + assembly { + x := 1 + y := 0xa + z := "Hello World!" + } + + return (x, y, z); + } + + function inlineFunction() public returns (uint256) { + uint256 result; + assembly { + function sum(a, b) -> c { + c := add(a, b) + } + + function multiply(a, b) -> c { + for { let i := 0 } lt(i, b) { i := add(i, 1) } { c := add(c, a) } + } + + result := sum(2, 3) + result := multiply(result, 5) + } + return result; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FooTest.sol", + r#" +import "./test.sol"; +import {Foo} from "./Foo.sol"; + +contract FooTest is DSTest { + function test_foo_coverage() external { + Foo foo = new Foo(); + foo.switchAndIfStatements(0); + foo.switchAndIfStatements(1); + foo.switchAndIfStatements(2); + foo.yulForLoop(2); + foo.hello(); + foo.readDynamicArrayLength(); + foo.inlineFunction(); + } +} + "#, + ) + .unwrap(); + + cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( + str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|-----------------|-----------------|---------------|---------------| +| src/Foo.sol | 100.00% (23/23) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +| Total | 100.00% (23/23) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | + +"#]], + ); +}); + +forgetest!(test_misc_coverage, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "Foo.sol", + r#" +struct Custom { + int256 f1; +} + +contract A { + function f(Custom memory custom) public returns (int256) { + return custom.f1; + } +} + +contract B { + uint256 public x; + + constructor(uint256 a) payable { + x = a; + } +} + +contract C { + function create() public { + B b = new B{value: 1}(2); + b = (new B{value: 1})(2); + b = (new B){value: 1}(2); + } +} + +contract D { + uint256 index; + + function g() public { + (uint256 x,, uint256 y) = (7, true, 2); + (x, y) = (y, x); + (index,,) = (7, true, 2); + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FooTest.sol", + r#" +import "./test.sol"; +import "./Foo.sol"; + +interface Vm { + function deal(address account, uint256 newBalance) external; +} + +contract FooTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_member_access_coverage() external { + A a = new A(); + Custom memory cust = Custom(1); + a.f(cust); + } + + function test_new_expression_coverage() external { + B b = new B(1); + b.x(); + C c = new C(); + vm.deal(address(c), 100 ether); + c.create(); + } + + function test_tuple_coverage() external { + D d = new D(); + d.g(); + } +} + "#, + ) + .unwrap(); + + cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( + str![[r#" +... +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|---------------|---------------|---------------|---------------| +| src/Foo.sol | 100.00% (8/8) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +| Total | 100.00% (8/8) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | + +"#]], + ); }); diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 0df24f88b..6aaa4f536 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -4,15 +4,15 @@ use crate::{ constants::*, utils::{self, EnvExternalities}, }; -use alloy_primitives::Address; +use alloy_primitives::{hex, Address}; use anvil::{spawn, NodeConfig}; use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_config::Config; use foundry_test_utils::{ - forgetest, forgetest_async, - util::{OutputExt, TestCommand, TestProject}, + forgetest, forgetest_async, str, + util::{TestCommand, TestProject}, }; -use std::{path::PathBuf, str::FromStr}; +use std::str::FromStr; /// This will insert _dummy_ contract that uses a library /// @@ -150,15 +150,22 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { pk.as_str(), ]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract.stdout"), - ); + cmd.assert().stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract-2nd.stdout"), - ); +"#]]); + + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // tests that we can deploy the template contract @@ -183,15 +190,21 @@ forgetest_async!(can_create_using_unlocked, |prj, cmd| { "--unlocked", ]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), - ); + cmd.assert().stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] + +"#]]); + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // tests that we can deploy with constructor args @@ -221,21 +234,26 @@ contract ConstructorContract { ) .unwrap(); - cmd.forge_fuse().args([ - "create", - "./src/ConstructorContract.sol:ConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "My Constructor", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_constructor_args.stdout"), - ); + cmd.forge_fuse() + .args([ + "create", + "./src/ConstructorContract.sol:ConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "My Constructor", + ]) + .assert_success() + .stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] + +"#]]); prj.add_source( "TupleArrayConstructorContract", @@ -252,21 +270,26 @@ contract TupleArrayConstructorContract { ) .unwrap(); - cmd.forge_fuse().args([ - "create", - "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "[(1,2), (2,3), (3,4)]", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), - ); + cmd.forge_fuse() + .args([ + "create", + "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]) + .assert() + .stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 889000ad5..8aadad62f 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -10,7 +10,14 @@ fn forge_std() { #[test] fn solmate() { - ExtTester::new("transmissions11", "solmate", "c892309933b25c03d32b1b0d674df7ae292ba925").run(); + let mut tester = + ExtTester::new("transmissions11", "solmate", "c892309933b25c03d32b1b0d674df7ae292ba925"); + + if cfg!(feature = "isolate-by-default") { + tester = tester.args(["--nmc", "ReentrancyGuardTest"]); + } + + tester.run(); } #[test] @@ -36,15 +43,25 @@ fn prb_proxy() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn sablier_v2() { - ExtTester::new("sablier-labs", "v2-core", "84758a40077bf3ccb1c8f7bb8d00278e672fbfef") - // Skip fork tests. - .args(["--nmc", "Fork"]) - // Run tests without optimizations. - .env("FOUNDRY_PROFILE", "lite") - .install_command(&["bun", "install", "--prefer-offline"]) - // Try npm if bun fails / is not installed. - .install_command(&["npm", "install", "--prefer-offline"]) - .run(); + let mut tester = + ExtTester::new("sablier-labs", "v2-core", "84758a40077bf3ccb1c8f7bb8d00278e672fbfef") + // Skip fork tests. + .args(["--nmc", "Fork"]) + // Increase the gas limit: https://github.com/sablier-labs/v2-core/issues/956 + .args(["--gas-limit", u64::MAX.to_string().as_str()]) + // Run tests without optimizations. + .env("FOUNDRY_PROFILE", "lite") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]); + + // This test reverts due to memory limit without isolation. This revert is not reached with + // isolation because memory is divided between separate EVMs created by inner calls. + if cfg!(feature = "isolate-by-default") { + tester = tester.args(["--nmt", "test_RevertWhen_LoopCalculationOverflowsBlockGasLimit"]); + } + + tester.run(); } #[test] @@ -56,6 +73,7 @@ fn solady() { #[cfg_attr(windows, ignore = "weird git fail")] fn geb() { ExtTester::new("reflexer-labs", "geb", "1a59f16a377386c49f520006ed0f7fd9d128cb09") + .env("FOUNDRY_LEGACY_ASSERTIONS", "true") .args(["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"]) .run(); } @@ -81,7 +99,7 @@ fn lil_web3() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn snekmate() { - ExtTester::new("pcaversaccio", "snekmate", "1aa50098720d49e04b257a4aa5138b3d737a0667") + ExtTester::new("pcaversaccio", "snekmate", "316088761ca7605216b5bfbbecca8d694c61ed98") .install_command(&["pnpm", "install", "--prefer-offline"]) // Try npm if pnpm fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) @@ -104,6 +122,7 @@ fn mds1_multicall() { fn drai() { ExtTester::new("mds1", "drai", "f31ce4fb15bbb06c94eefea2a3a43384c75b95cf") .args(["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"]) + .env("FOUNDRY_LEGACY_ASSERTIONS", "true") .fork_block(13633752) .run(); } diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index 147bbae1b..bf8bdce56 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -4,6 +4,7 @@ extern crate foundry_test_utils; pub mod constants; pub mod utils; +mod bind_json; mod build; mod cache; mod cmd; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 3483a9c0c..d2475e374 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,7 +1,7 @@ //! Contains various tests related to `forge script`. use crate::{constants::TEMPLATE_CONTRACT, zksync_node}; -use alloy_primitives::{Address, Bytes}; +use alloy_primitives::{hex, Address, Bytes}; use anvil::{spawn, NodeConfig}; use foundry_test_utils::{rpc, util::OutputExt, ScriptOutcome, ScriptTester}; use regex::Regex; diff --git a/crates/forge/tests/cli/soldeer.rs b/crates/forge/tests/cli/soldeer.rs index 6bbba534d..1a62578d1 100644 --- a/crates/forge/tests/cli/soldeer.rs +++ b/crates/forge/tests/cli/soldeer.rs @@ -11,6 +11,8 @@ forgesoldeer!(install_dependency, |prj, cmd| { let command = "install"; let dependency = "forge-std~1.8.1"; + let foundry_file = prj.root().join("foundry.toml"); + cmd.arg("soldeer").args([command, dependency]); cmd.execute(); @@ -34,7 +36,49 @@ checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" assert_eq!(lock_contents, actual_lock_contents); // Making sure the foundry contents are the right ones + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = "1.8.1" +"#; + + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); +}); + +forgesoldeer!(install_dependency_git, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + let git = "git@gitlab.com:mario4582928/Mario.git"; + let foundry_file = prj.root().join("foundry.toml"); + + cmd.arg("soldeer").args([command, dependency, git]); + cmd.execute(); + + // Making sure the path was created to the dependency and that README.md exists + // meaning that the dependencies were installed correctly + let path_dep_forge = prj.root().join("dependencies").join("forge-std-1.8.1").join("README.md"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + let lock_contents = r#" +[[dependencies]] +name = "forge-std" +version = "1.8.1" +source = "git@gitlab.com:mario4582928/Mario.git" +checksum = "22868f426bd4dd0e682b5ec5f9bd55507664240c" +"#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert_eq!(lock_contents, actual_lock_contents); + + // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] src = "src" out = "out" @@ -43,11 +87,56 @@ libs = ["lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options [dependencies] -forge-std = { version = "1.8.1" } +forge-std = { version = "1.8.1", git = "git@gitlab.com:mario4582928/Mario.git", rev = "22868f426bd4dd0e682b5ec5f9bd55507664240c" } +"#; + + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); +}); + +forgesoldeer!(install_dependency_git_commit, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + let git = "git@gitlab.com:mario4582928/Mario.git"; + let rev_flag = "--rev"; + let commit = "7a0663eaf7488732f39550be655bad6694974cb3"; + + let foundry_file = prj.root().join("foundry.toml"); + + cmd.arg("soldeer").args([command, dependency, git, rev_flag, commit]); + cmd.execute(); + + // Making sure the path was created to the dependency and that README.md exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("JustATest2.md"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + let lock_contents = r#" +[[dependencies]] +name = "forge-std" +version = "1.8.1" +source = "git@gitlab.com:mario4582928/Mario.git" +checksum = "7a0663eaf7488732f39550be655bad6694974cb3" +"#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert_eq!(lock_contents, actual_lock_contents); + + // Making sure the foundry contents are the right ones + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = { version = "1.8.1", git = "git@gitlab.com:mario4582928/Mario.git", rev = "7a0663eaf7488732f39550be655bad6694974cb3" } "#; - let actual_foundry_contents = read_file_to_string(&foundry_file); - assert_eq!(foundry_contents, actual_foundry_contents); + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); }); forgesoldeer!(update_dependencies, |prj, cmd| { @@ -100,8 +189,7 @@ libs = ["lib"] forge-std = { version = "1.8.1" } "#; - let actual_foundry_contents = read_file_to_string(&foundry_file); - assert_eq!(foundry_contents, actual_foundry_contents); + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); }); forgesoldeer!(update_dependencies_simple_version, |prj, cmd| { @@ -155,8 +243,7 @@ libs = ["lib"] forge-std = "1.8.1" "#; - let actual_foundry_contents = read_file_to_string(&foundry_file); - assert_eq!(foundry_contents, actual_foundry_contents); + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); }); forgesoldeer!(login, |prj, cmd| { diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index cbdd56f9d..8403053d8 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 25); +const LATEST_SOLC: Version = Version::new(0, 8, 26); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index e78c8abc5..205e89737 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -3,7 +3,7 @@ use alloy_primitives::U256; use foundry_config::{Config, FuzzConfig}; use foundry_test_utils::{ - rpc, + rpc, str, util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}, }; use std::{path::PathBuf, str::FromStr}; @@ -21,6 +21,7 @@ forgetest!(can_set_filter_values, |prj, cmd| { contract_pattern_inverse: None, path_pattern: Some(glob.clone()), path_pattern_inverse: None, + coverage_pattern_inverse: None, ..Default::default() }; prj.write_config(config); @@ -33,6 +34,7 @@ forgetest!(can_set_filter_values, |prj, cmd| { assert_eq!(config.contract_pattern_inverse, None); assert_eq!(config.path_pattern.unwrap(), glob); assert_eq!(config.path_pattern_inverse, None); + assert_eq!(config.coverage_pattern_inverse, None); }); // tests that warning is displayed when there are no tests in project @@ -153,7 +155,7 @@ forgetest!(can_test_with_match_path, |prj, cmd| { r#" import "./test.sol"; contract ATest is DSTest { - function testArray(uint64[2] calldata values) external { + function testPass() external { assertTrue(true); } } @@ -174,8 +176,57 @@ contract FailTest is DSTest { ) .unwrap(); - cmd.args(["test", "--match-path", "*src/ATest.t.sol"]); - assert!(cmd.stdout_lossy().contains("[PASS]") && !cmd.stdout_lossy().contains("[FAIL]")); + cmd.args(["test", "--match-path", "*src/ATest.t.sol"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testPass() (gas: 190) +... +Ran 1 test suite in [..] 1 tests passed, 0 failed, 0 skipped (1 total tests) +... +"#]]); +}); + +// tests that using the --match-path option works with absolute paths +forgetest!(can_test_with_match_path_absolute, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "ATest.t.sol", + r#" +import "./test.sol"; +contract ATest is DSTest { + function testPass() external { + assertTrue(true); + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FailTest.t.sol", + r#" +import "./test.sol"; +contract FailTest is DSTest { + function testNothing() external { + assertTrue(false); + } +} + "#, + ) + .unwrap(); + + let test_path = prj.root().join("src/ATest.t.sol"); + let test_path = test_path.to_string_lossy(); + + cmd.args(["test", "--match-path", test_path.as_ref()]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testPass() (gas: 190) +... +Ran 1 test suite in [..] 1 tests passed, 0 failed, 0 skipped (1 total tests) +... +"#]]); }); // tests that `forge test` will pick up tests that are stored in the `test = ` config value @@ -209,6 +260,7 @@ contract MyTest is DSTest { }); // checks that forge test repeatedly produces the same output +#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_test_repeatedly, |_prj, cmd| { cmd.arg("test"); cmd.assert_non_empty_stdout(); @@ -264,6 +316,7 @@ contract ContractTest is DSTest { }); // tests that libraries are handled correctly in multiforking mode +#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { prj.wipe_contracts(); @@ -544,7 +597,7 @@ contract Dummy { .unwrap(); cmd.args(["test", "--match-path", "src/dummy.sol"]); - cmd.assert_success() + cmd.assert_success(); }); forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { @@ -587,12 +640,386 @@ contract CounterTest is Test { cmd.args(["test"]); let (stderr, _) = cmd.unchecked_output_lossy(); + // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs) + assert_eq!(extract_number_of_runs(stderr), 61); +}); + +forgetest_init!(should_exit_early_on_invariant_failure, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "CounterInvariant.t.sol", + r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract Counter { + uint256 public number = 0; + + function inc() external { + number += 1; + } +} + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_early_exit() public view { + assertTrue(counter.number() == 10, "wrong count"); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]); + let (stderr, _) = cmd.unchecked_output_lossy(); + // make sure invariant test exit early with 0 runs + assert_eq!(extract_number_of_runs(stderr), 0); +}); + +fn extract_number_of_runs(stderr: String) -> usize { let runs = stderr.find("runs:").and_then(|start_runs| { let runs_split = &stderr[start_runs + 6..]; runs_split.find(',').map(|end_runs| &runs_split[..end_runs]) }); - // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs) - assert_eq!(runs.unwrap().parse::().unwrap(), 61); + runs.unwrap().parse::().unwrap() +} + +forgetest_init!(should_replay_failures_only, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "ReplayFailures.t.sol", + r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract ReplayFailuresTest is Test { + function testA() public pure { + require(2 > 1); + } + + function testB() public pure { + require(1 > 2, "testB failed"); + } + + function testC() public pure { + require(2 > 1); + } + + function testD() public pure { + require(1 > 2, "testD failed"); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]); + cmd.assert_err(); + // Test failure filter should be persisted. + assert!(prj.root().join("cache/test-failures").exists()); + + // Perform only the 2 failing tests from last run. + cmd.forge_fuse(); + cmd.args(["test", "--rerun"]).unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/replay_last_run_failures.stdout"), + ); +}); + +// +forgetest_init!(should_show_precompile_labels, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +contract PrecompileLabelsTest is Test { + function testPrecompileLabels() public { + vm.deal(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D), 1 ether); + vm.deal(address(0x000000000000000000636F6e736F6c652e6c6f67), 1 ether); + vm.deal(address(0x4e59b44847b379578588920cA78FbF26c0B4956C), 1 ether); + vm.deal(address(0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38), 1 ether); + vm.deal(address(0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84), 1 ether); + vm.deal(address(1), 1 ether); + vm.deal(address(2), 1 ether); + vm.deal(address(3), 1 ether); + vm.deal(address(4), 1 ether); + vm.deal(address(5), 1 ether); + vm.deal(address(6), 1 ether); + vm.deal(address(7), 1 ether); + vm.deal(address(8), 1 ether); + vm.deal(address(9), 1 ether); + vm.deal(address(10), 1 ether); + } +} + "#, + ) + .unwrap(); + + let output = cmd.args(["test", "-vvvv"]).stdout_lossy(); + assert!(output.contains("VM: [0x7109709ECfa91a80626fF3989D68f67F5b1DD12D]")); + assert!(output.contains("console: [0x000000000000000000636F6e736F6c652e6c6f67]")); + assert!(output.contains("Create2Deployer: [0x4e59b44847b379578588920cA78FbF26c0B4956C]")); + assert!(output.contains("DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38]")); + assert!(output.contains("DefaultTestContract: [0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84]")); + assert!(output.contains("ECRecover: [0x0000000000000000000000000000000000000001]")); + assert!(output.contains("SHA-256: [0x0000000000000000000000000000000000000002]")); + assert!(output.contains("RIPEMD-160: [0x0000000000000000000000000000000000000003]")); + assert!(output.contains("Identity: [0x0000000000000000000000000000000000000004]")); + assert!(output.contains("ModExp: [0x0000000000000000000000000000000000000005]")); + assert!(output.contains("ECAdd: [0x0000000000000000000000000000000000000006]")); + assert!(output.contains("ECMul: [0x0000000000000000000000000000000000000007]")); + assert!(output.contains("ECPairing: [0x0000000000000000000000000000000000000008]")); + assert!(output.contains("Blake2F: [0x0000000000000000000000000000000000000009]")); + assert!(output.contains("PointEvaluation: [0x000000000000000000000000000000000000000A]")); +}); + +// tests that `forge test` with config `show_logs: true` for fuzz tests will +// display `console.log` info +forgetest_init!(should_show_logs_when_fuzz_test, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + let config = Config { + fuzz: { FuzzConfig { runs: 3, show_logs: true, ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#"pragma solidity 0.8.24; + import {Test, console2} from "forge-std/Test.sol"; + contract ContractFuzz is Test { + function testFuzzConsoleLog(uint256 x) public pure { + console2.log("inside fuzz test, x is:", x); + } + } + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]); + let stdout = cmd.stdout_lossy(); + assert!(stdout.contains("inside fuzz test, x is:"), "\n{stdout}"); +}); + +// tests that `forge test` with inline config `show_logs = true` for fuzz tests will +// display `console.log` info +forgetest_init!(should_show_logs_when_fuzz_test_inline_config, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + let config = + Config { fuzz: { FuzzConfig { runs: 3, ..Default::default() } }, ..Default::default() }; + prj.write_config(config); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#"pragma solidity 0.8.24; + import {Test, console2} from "forge-std/Test.sol"; + contract ContractFuzz is Test { + + /// forge-config: default.fuzz.show-logs = true + function testFuzzConsoleLog(uint256 x) public pure { + console2.log("inside fuzz test, x is:", x); + } + } + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]); + let stdout = cmd.stdout_lossy(); + assert!(stdout.contains("inside fuzz test, x is:"), "\n{stdout}"); +}); + +// tests that `forge test` with config `show_logs: false` for fuzz tests will not display +// `console.log` info +forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + let config = Config { + fuzz: { FuzzConfig { runs: 3, show_logs: false, ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#"pragma solidity 0.8.24; + import {Test, console2} from "forge-std/Test.sol"; + contract ContractFuzz is Test { + + function testFuzzConsoleLog(uint256 x) public pure { + console2.log("inside fuzz test, x is:", x); + } + } + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]); + let stdout = cmd.stdout_lossy(); + assert!(!stdout.contains("inside fuzz test, x is:"), "\n{stdout}"); +}); + +// tests that `forge test` with inline config `show_logs = false` for fuzz tests will not +// display `console.log` info +forgetest_init!(should_not_show_logs_when_fuzz_test_inline_config, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + let config = + Config { fuzz: { FuzzConfig { runs: 3, ..Default::default() } }, ..Default::default() }; + prj.write_config(config); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#"pragma solidity 0.8.24; + import {Test, console2} from "forge-std/Test.sol"; + contract ContractFuzz is Test { + + /// forge-config: default.fuzz.show-logs = false + function testFuzzConsoleLog(uint256 x) public pure { + console2.log("inside fuzz test, x is:", x); + } + } + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]); + let stdout = cmd.stdout_lossy(); + assert!(!stdout.contains("inside fuzz test, x is:"), "\n{stdout}"); +}); + +// tests internal functions trace +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#"pragma solidity 0.8.24; + import {Test, console2} from "forge-std/Test.sol"; +contract SimpleContract { + uint256 public num; + address public addr; + + function _setNum(uint256 _num) internal returns(uint256 prev) { + prev = num; + num = _num; + } + + function _setAddr(address _addr) internal returns(address prev) { + prev = addr; + addr = _addr; + } + + function increment() public { + _setNum(num + 1); + } + + function setValues(uint256 _num, address _addr) public { + _setNum(_num); + _setAddr(_addr); + } +} + +contract SimpleContractTest is Test { + function test() public { + SimpleContract c = new SimpleContract(); + c.increment(); + c.setValues(100, address(0x123)); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#" +... +Traces: + [250463] SimpleContractTest::test() + ├─ [171014] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 854 bytes of code + ├─ [22638] SimpleContract::increment() + │ ├─ [20150] SimpleContract::_setNum(1) + │ │ └─ ← 0 + │ └─ ← [Stop] + ├─ [23219] SimpleContract::setValues(100, 0x0000000000000000000000000000000000000123) + │ ├─ [250] SimpleContract::_setNum(100) + │ │ └─ ← 1 + │ ├─ [22339] SimpleContract::_setAddr(0x0000000000000000000000000000000000000123) + │ │ └─ ← 0x0000000000000000000000000000000000000000 + │ └─ ← [Stop] + └─ ← [Stop] +... +"#]]); +}); + +// tests internal functions trace with memory decoding +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#"pragma solidity 0.8.24; +import {Test, console2} from "forge-std/Test.sol"; + +contract SimpleContract { + string public str = "initial value"; + + function _setStr(string memory _str) internal returns(string memory prev) { + prev = str; + str = _str; + } + + function setStr(string memory _str) public { + _setStr(_str); + } +} + +contract SimpleContractTest is Test { + function test() public { + SimpleContract c = new SimpleContract(); + c.setStr("new value"); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vvvv", "--decode-internal", "test"]).assert_success().stdout_eq(str![[ + r#" +... +Traces: + [421960] SimpleContractTest::test() + ├─ [385978] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 1814 bytes of code + ├─ [2534] SimpleContract::setStr("new value") + │ ├─ [1600] SimpleContract::_setStr("new value") + │ │ └─ ← "initial value" + │ └─ ← [Stop] + └─ ← [Stop] +... +"# + ]]); }); // Related to: https://github.com/matter-labs/foundry-zksync/issues/478 diff --git a/crates/forge/tests/cli/zksync_node.rs b/crates/forge/tests/cli/zksync_node.rs index b01c8a558..640884335 100644 --- a/crates/forge/tests/cli/zksync_node.rs +++ b/crates/forge/tests/cli/zksync_node.rs @@ -143,22 +143,13 @@ impl ZkSyncNode { let node: InMemoryNode = InMemoryNode::new(None, None, Default::default()); - tracing::info!(""); - tracing::info!("Rich Accounts"); - tracing::info!("============="); for wallet in LEGACY_RICH_WALLETS.iter() { let address = wallet.0; node.set_rich_account(H160::from_str(address).unwrap()); } - for (index, wallet) in RICH_WALLETS.iter().enumerate() { + for wallet in RICH_WALLETS.iter() { let address = wallet.0; - let private_key = wallet.1; - let mnemonic_phrase = wallet.2; node.set_rich_account(H160::from_str(address).unwrap()); - tracing::info!("Account #{}: {} ({})", index, address, "1_000_000_000_000 ETH"); - tracing::info!("Private Key: {}", private_key); - tracing::info!("Mnemonic: {}", &mnemonic_phrase); - tracing::info!(""); } let mut io = IoHandler::default(); diff --git a/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout b/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout deleted file mode 100644 index c04ae5bd5..000000000 --- a/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout +++ /dev/null @@ -1,4 +0,0 @@ -No files changed, compilation skipped -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x3d78b08c411f05d5e79adc92a4c814e0f818d1a09c111b0ab688270f35a07ae7 diff --git a/crates/forge/tests/fixtures/can_create_template_contract.stdout b/crates/forge/tests/fixtures/can_create_template_contract.stdout deleted file mode 100644 index 533c92727..000000000 --- a/crates/forge/tests/fixtures/can_create_template_contract.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 2.27s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x4c3d9f7c4cc26876b43a11ba7ff218374471786a8ae8bf5574deb1d97fc1e851 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout deleted file mode 100644 index c04ae5bd5..000000000 --- a/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout +++ /dev/null @@ -1,4 +0,0 @@ -No files changed, compilation skipped -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x3d78b08c411f05d5e79adc92a4c814e0f818d1a09c111b0ab688270f35a07ae7 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout deleted file mode 100644 index 1f8b60d6f..000000000 --- a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 1.95s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x4c3d9f7c4cc26876b43a11ba7ff218374471786a8ae8bf5574deb1d97fc1e851 diff --git a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout deleted file mode 100644 index 299ad2f2d..000000000 --- a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 2.82s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x294df85109c991ec2760cd51e5ddc869bf5dc3b249b296305ffcd1a0563b2eea diff --git a/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout deleted file mode 100644 index a0a574c95..000000000 --- a/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 26.44ms -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x69625b76d83634603a9dbc5b836ef89bafdd9fc7c180fc6d636c5088353cf501 diff --git a/crates/forge/tests/fixtures/can_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout index 7095a50f0..5a29d4dd7 100644 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ b/crates/forge/tests/fixtures/can_test_repeatedly.stdout @@ -2,7 +2,7 @@ No files changed, compilation skipped Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 26521, ~: 28387) -[PASS] test_Increment() (gas: 31325) +[PASS] test_Increment() (gas: 31303) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms Ran 1 test suite: 2 tests passed, 0 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/compile_json.stdout b/crates/forge/tests/fixtures/compile_json.stdout index eff78e60c..2a794cc4a 100644 --- a/crates/forge/tests/fixtures/compile_json.stdout +++ b/crates/forge/tests/fixtures/compile_json.stdout @@ -15,5 +15,6 @@ "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/jsonError.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n" } ], - "sources": {} + "sources": {}, + "build_infos": ["{...}"] } \ No newline at end of file diff --git a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout index 571cc6927..9b289543f 100644 --- a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout +++ b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout @@ -3,14 +3,14 @@ Solc 0.8.23 finished in 798.51ms Compiler run successful! Ran 2 tests for test/Contract.t.sol:CustomTypesTest -[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 231) +[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 254) Traces: - [231] CustomTypesTest::testErr() + [254] CustomTypesTest::testErr() └─ ← [Revert] PoolNotInitialized() -[PASS] testEvent() (gas: 1312) +[PASS] testEvent() (gas: 1268) Traces: - [1312] CustomTypesTest::testEvent() + [1268] CustomTypesTest::testEvent() ├─ emit MyEvent(a: 100) └─ ← [Stop] @@ -20,6 +20,6 @@ Ran 1 test suite: 1 tests passed, 1 failed, 0 skipped (2 total tests) Failing tests: Encountered 1 failing test in test/Contract.t.sol:CustomTypesTest -[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 231) +[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 254) Encountered a total of 1 failing tests, 1 tests succeeded diff --git a/crates/forge/tests/fixtures/replay_last_run_failures.stdout b/crates/forge/tests/fixtures/replay_last_run_failures.stdout new file mode 100644 index 000000000..d94983059 --- /dev/null +++ b/crates/forge/tests/fixtures/replay_last_run_failures.stdout @@ -0,0 +1,15 @@ +No files changed, compilation skipped + +Ran 2 tests for test/ReplayFailures.t.sol:ReplayFailuresTest +[FAIL. Reason: revert: testB failed] testB() (gas: 303) +[FAIL. Reason: revert: testD failed] testD() (gas: 314) +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; finished in 555.95µs (250.31µs CPU time) + +Ran 1 test suite in 3.24ms (555.95µs CPU time): 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest +[FAIL. Reason: revert: testB failed] testB() (gas: 303) +[FAIL. Reason: revert: testD failed] testD() (gas: 314) + +Encountered a total of 2 failing tests, 0 tests succeeded diff --git a/crates/forge/tests/fixtures/repro_6531.stdout b/crates/forge/tests/fixtures/repro_6531.stdout index 35c27c948..47a6bb237 100644 --- a/crates/forge/tests/fixtures/repro_6531.stdout +++ b/crates/forge/tests/fixtures/repro_6531.stdout @@ -3,9 +3,9 @@ Compiling 1 files with 0.8.23 Compiler run successful! Ran 1 test for test/Contract.t.sol:USDTCallingTest -[PASS] test() (gas: 9559) +[PASS] test() (gas: 9537) Traces: - [9559] USDTCallingTest::test() + [9537] USDTCallingTest::test() ├─ [0] VM::createSelectFork("") │ └─ ← [Return] 0 ├─ [3110] 0xdAC17F958D2ee523a2206206994597C13D831ec7::name() [staticcall] diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 47d6ebbb9..2bbbee902 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -21,6 +21,10 @@ async fn test_cheats_local(test_data: &ForgeTestData) { filter = filter.exclude_tests("(Ffi|File|Line|Root)"); } + if cfg!(feature = "isolate-by-default") { + filter = filter.exclude_contracts("LastCallGasDefaultTest"); + } + let mut config = test_data.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); let runner = test_data.runner_with_config(config); diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index 1b2a1398d..9cabd998a 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -7,7 +7,7 @@ use forge::{ use foundry_evm::{ decode::decode_console_logs, revm::primitives::SpecId, - traces::{render_trace_arena, CallTraceDecoderBuilder}, + traces::{decode_trace_arena, render_trace_arena, CallTraceDecoderBuilder}, }; use foundry_test_utils::{init_tracing, Filter}; use futures::future::join_all; @@ -65,24 +65,27 @@ impl TestConfig { eyre::bail!("empty test result"); } for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, result) in test_results { + for (test_name, mut result) in test_results { if self.should_fail && (result.status == TestStatus::Success) || !self.should_fail && (result.status == TestStatus::Failure) { let logs = decode_console_logs(&result.logs); let outcome = if self.should_fail { "fail" } else { "pass" }; - let call_trace_decoder = CallTraceDecoderBuilder::default().build(); - let decoded_traces = join_all( - result - .traces - .iter() - .map(|(_, a)| render_trace_arena(a, &call_trace_decoder)) - .collect::>(), - ) + let call_trace_decoder = CallTraceDecoderBuilder::default() + .with_known_contracts(&self.runner.known_contracts) + .build(); + let decoded_traces = join_all(result.traces.iter_mut().map(|(_, arena)| { + let decoder = &call_trace_decoder; + async move { + decode_trace_arena(arena, decoder) + .await + .expect("Failed to decode traces"); + render_trace_arena(arena) + } + })) .await .into_iter() - .map(|x| x.unwrap()) - .collect::>(); + .collect::>(); eyre::bail!( "Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}\n\nTraces:\n{}", test_name, @@ -122,7 +125,7 @@ pub fn assert_multiple( "We did not run as many test functions as we expected for {contract_name}" ); for (test_name, should_pass, reason, expected_logs, expected_warning_count) in tests { - let logs = &actuals[*contract_name].test_results[*test_name].decoded_logs; + let logs = &decode_console_logs(&actuals[*contract_name].test_results[*test_name].logs); let warnings_count = &actuals[*contract_name].warnings.len(); diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index 4c6fae77c..17d9f59d8 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -89,6 +89,23 @@ async fn test_core() { "default/core/BadSigAfterInvariant.t.sol:BadSigAfterInvariant", vec![("testShouldPassWithWarning()", true, None, None, None)], ), + ( + "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", + vec![( + "testMultipleAssertFailures()", + false, + Some("assertion failed: 1 != 2".to_string()), + None, + None, + )], + ), + ( + "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", + vec![ + ("testFlagNotSetSuccess()", true, None, None, None), + ("testFlagSetFailure()", true, None, None, None), + ], + ), ]), ); } @@ -740,3 +757,49 @@ async fn test_trace() { } } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_assertions_revert_false() { + let filter = Filter::new(".*", ".*NoAssertionsRevertTest", ".*"); + let mut config = TEST_DATA_DEFAULT.config.clone(); + config.assertions_revert = false; + let mut runner = TEST_DATA_DEFAULT.runner_with_config(config); + let results = runner.test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([( + "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", + vec![( + "testMultipleAssertFailures()", + false, + None, + Some(vec![ + "assertion failed: 1 != 2".to_string(), + "assertion failed: 5 >= 4".to_string(), + ]), + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_legacy_assertions() { + let filter = Filter::new(".*", ".*LegacyAssertions", ".*"); + let mut config = TEST_DATA_DEFAULT.config.clone(); + config.legacy_assertions = true; + let mut runner = TEST_DATA_DEFAULT.runner_with_config(config); + let results = runner.test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([( + "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", + vec![ + ("testFlagNotSetSuccess()", true, None, None, None), + ("testFlagSetFailure()", false, None, None, None), + ], + )]), + ); +} diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 4f8a6d412..f1c5edaaa 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -3,6 +3,7 @@ use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::{Bytes, U256}; use forge::{ + decode::decode_console_logs, fuzz::CounterExample, result::{SuiteResult, TestStatus}, }; @@ -31,7 +32,7 @@ async fn test_fuzz() { "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, - result.decoded_logs.join("\n") + decode_console_logs(&result.logs).join("\n") ), _ => assert_eq!( result.status, @@ -39,7 +40,7 @@ async fn test_fuzz() { "Test {} did not fail as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, - result.decoded_logs.join("\n") + decode_console_logs(&result.logs).join("\n") ), } } @@ -67,7 +68,7 @@ async fn test_successful_fuzz_cases() { "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, - result.decoded_logs.join("\n") + decode_console_logs(&result.logs).join("\n") ), _ => {} } diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 09d4fb323..ed7729f7f 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -10,12 +10,39 @@ async fn inline_config_run_fuzz() { let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); let result = runner.test_collect(&filter); - let suite_result = result.get("default/inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); - let test_result = suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); - match test_result.kind { - TestKind::Fuzz { runs, .. } => assert_eq!(runs, 1024), - _ => unreachable!(), - } + let results = result + .into_iter() + .flat_map(|(path, r)| { + r.test_results.into_iter().map(move |(name, t)| { + let runs = match t.kind { + TestKind::Fuzz { runs, .. } => runs, + _ => unreachable!(), + }; + (path.clone(), name, runs) + }) + }) + .collect::>(); + + assert_eq!( + results, + vec![ + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(), + "testInlineConfFuzz(uint8)".to_string(), + 1024 + ), + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), + "testInlineConfFuzz1(uint8)".to_string(), + 1 + ), + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), + "testInlineConfFuzz2(uint8)".to_string(), + 10 + ), + ] + ); } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 45bcda9d5..ceac2ba24 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -2,7 +2,7 @@ use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::U256; -use forge::{fuzz::CounterExample, TestOptions}; +use forge::fuzz::CounterExample; use foundry_test_utils::Filter; use std::collections::BTreeMap; @@ -24,48 +24,40 @@ macro_rules! get_counterexample { } #[tokio::test(flavor = "multi_thread")] -async fn test_invariant() { - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = TEST_DATA_DEFAULT.test_opts.clone(); - runner.test_options.invariant.failure_persist_dir = - Some(tempfile::tempdir().unwrap().into_path()); - let results = runner.test_collect(&filter); - +async fn test_invariant_with_alias() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantTest1.t.sol"); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); assert_multiple( &results, - BTreeMap::from([ - ( - "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", - vec![("statefulFuzz_BrokenInvariant()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", - vec![( - "invariantHideJesus()", + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", + vec![ + ("invariant_neverFalse()", false, Some("revert: false".into()), None, None), + ( + "statefulFuzz_neverFalseWithInvariantAlias()", false, - Some("revert: jesus betrayed".into()), + Some("revert: false".into()), None, None, - )], - ), - ( - "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", - vec![("invariantNotStolen()", true, None, None, None)], - ), - ( - "default/fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", - vec![ - ("invariant_neverFalse()", false, Some("revert: false".into()), None, None), - ( - "statefulFuzz_neverFalseWithInvariantAlias()", - false, - Some("revert: false".into()), - None, - None, - ), - ], - ), + ), + ], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_filters() { + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.runs = 10; + + // Contracts filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/target/(ExcludeContracts|TargetContracts).t.sol", + )), + BTreeMap::from([ ( "default/fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", vec![("invariantTrueWorld()", true, None, None, None)], @@ -74,18 +66,23 @@ async fn test_invariant() { "default/fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", vec![("invariantTrueWorld()", true, None, None, None)], ), + ]), + ); + + // Senders filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/target/(ExcludeSenders|TargetSenders).t.sol", + )), + BTreeMap::from([ ( - "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", - vec![( - "invariantTrueWorld()", - false, - Some("revert: false world".into()), - None, - None, - )], + "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", + vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "default/fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", + "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", vec![( "invariantTrueWorld()", false, @@ -94,18 +91,49 @@ async fn test_invariant() { None, )], ), + ]), + ); + + // Interfaces filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/target/TargetInterfaces.t.sol", + )), + BTreeMap::from([( + "default/fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", + vec![("invariantTrueWorld()", false, Some("revert: false world".into()), None, None)], + )]), + ); + + // Selectors filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/target/(ExcludeSelectors|TargetSelectors).t.sol", + )), + BTreeMap::from([ ( - "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", - vec![("invariantTrueWorld()", true, None, None, None)], + "default/fuzz/invariant/target/ExcludeSelectors.t.sol:ExcludeSelectors", + vec![("invariantFalseWorld()", true, None, None, None)], ), ( "default/fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", vec![("invariantTrueWorld()", true, None, None, None)], ), - ( - "default/fuzz/invariant/target/ExcludeSelectors.t.sol:ExcludeSelectors", - vec![("invariantFalseWorld()", true, None, None, None)], - ), + ]), + ); + + // Artifacts filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/targetAbi/(ExcludeArtifacts|TargetArtifacts|TargetArtifactSelectors|TargetArtifactSelectors2).t.sol", + )), + BTreeMap::from([ ( "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", vec![("invariantShouldPass()", true, None, None, None)], @@ -284,7 +312,6 @@ async fn test_invariant_override() { runner.test_options.invariant.fail_on_revert = false; runner.test_options.invariant.call_override = true; let results = runner.test_collect(&filter); - assert_multiple( &results, BTreeMap::from([( @@ -302,7 +329,6 @@ async fn test_invariant_fail_on_revert() { runner.test_options.invariant.runs = 1; runner.test_options.invariant.depth = 10; let results = runner.test_collect(&filter); - assert_multiple( &results, BTreeMap::from([( @@ -326,7 +352,6 @@ async fn test_invariant_storage() { runner.test_options.invariant.depth = 100 + (50 * cfg!(windows) as u32); runner.test_options.fuzz.seed = Some(U256::from(6u32)); let results = runner.test_collect(&filter); - assert_multiple( &results, BTreeMap::from([( @@ -341,6 +366,25 @@ async fn test_invariant_storage() { ); } +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_inner_contract() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", + vec![( + "invariantHideJesus()", + false, + Some("revert: jesus betrayed".into()), + None, + None, + )], + )]), + ); +} + #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_invariant_shrink() { @@ -377,22 +421,18 @@ async fn test_invariant_shrink() { #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_invariant_assert_shrink() { - let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); - opts.fuzz.seed = Some(U256::from(119u32)); - // ensure assert and require shrinks to same sequence of 3 or less - test_shrink(opts.clone(), "InvariantShrinkWithAssert").await; - test_shrink(opts.clone(), "InvariantShrinkWithRequire").await; + test_shrink("invariant_with_assert").await; + test_shrink("invariant_with_require").await; } -async fn test_shrink(opts: TestOptions, contract_pattern: &str) { - let filter = Filter::new( - ".*", - contract_pattern, - ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol", - ); +async fn test_shrink(test_pattern: &str) { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(100u32)); + let filter = + Filter::new(test_pattern, ".*", ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = opts.clone(); + runner.test_options = opts; match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), @@ -405,15 +445,12 @@ async fn test_shrink(opts: TestOptions, contract_pattern: &str) { #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_shrink_big_sequence() { - let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); - opts.fuzz.seed = Some(U256::from(119u32)); - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = opts.clone(); + runner.test_options.fuzz.seed = Some(U256::from(119u32)); runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 500; + runner.test_options.invariant.depth = 1000; let initial_counterexample = runner .test_collect(&filter) @@ -480,16 +517,13 @@ async fn test_shrink_big_sequence() { #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_shrink_fail_on_revert() { - let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); - opts.fuzz.seed = Some(U256::from(119u32)); - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = opts.clone(); + runner.test_options.fuzz.seed = Some(U256::from(119u32)); runner.test_options.invariant.fail_on_revert = true; runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 100; + runner.test_options.invariant.depth = 200; match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), @@ -656,8 +690,7 @@ async fn test_invariant_fixtures() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_scrape_values() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantScrapeValues.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - let results = runner.test_collect(&filter); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); assert_multiple( &results, BTreeMap::from([ @@ -687,17 +720,10 @@ async fn test_invariant_scrape_values() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_roll_fork_handler() { - let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); - opts.fuzz.seed = Some(U256::from(119u32)); - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantRollFork.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = opts.clone(); - runner.test_options.invariant.failure_persist_dir = - Some(tempfile::tempdir().unwrap().into_path()); - + runner.test_options.fuzz.seed = Some(U256::from(119u32)); let results = runner.test_collect(&filter); - assert_multiple( &results, BTreeMap::from([ @@ -744,11 +770,7 @@ async fn test_invariant_excluded_senders() { async fn test_invariant_after_invariant() { // Check failure on passing invariant and failed `afterInvariant` condition let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAfterInvariant.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.failure_persist_dir = - Some(tempfile::tempdir().unwrap().into_path()); - - let results = runner.test_collect(&filter); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); assert_multiple( &results, BTreeMap::from([( @@ -776,17 +798,11 @@ async fn test_invariant_after_invariant() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_selectors_weight() { - let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); - opts.fuzz.seed = Some(U256::from(100u32)); - let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSelectorsWeight.t.sol"); let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options = opts.clone(); + runner.test_options.fuzz.seed = Some(U256::from(119u32)); runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 30; - runner.test_options.invariant.failure_persist_dir = - Some(tempfile::tempdir().unwrap().into_path()); - + runner.test_options.invariant.depth = 10; let results = runner.test_collect(&filter); assert_multiple( &results, @@ -796,3 +812,21 @@ async fn test_invariant_selectors_weight() { )]), ) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_no_reverts_in_counterexample() { + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSequenceNoReverts.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = false; + // Use original counterexample to test sequence len. + runner.test_options.invariant.shrink_run_limit = 0; + + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => { + // ensure original counterexample len is 10 (even without shrinking) + assert_eq!(sequence.len(), 10); + } + }; +} diff --git a/crates/forge/tests/it/main.rs b/crates/forge/tests/it/main.rs index 4e4ef0c73..4d0120bd2 100644 --- a/crates/forge/tests/it/main.rs +++ b/crates/forge/tests/it/main.rs @@ -10,4 +10,5 @@ mod inline; mod invariant; mod repros; mod spec; +mod vyper; mod zk; diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 8af97de10..3760e38bf 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -8,13 +8,11 @@ use crate::{ }; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; use alloy_json_abi::Event; -use alloy_primitives::{address, Address, U256}; -use forge::result::TestStatus; +use alloy_primitives::{address, b256, Address, U256}; +use forge::{decode::decode_console_logs, result::TestStatus}; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; -use foundry_evm::{ - constants::HARDHAT_CONSOLE_ADDRESS, - traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}, -}; +use foundry_evm::traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}; +use foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS; use foundry_test_utils::Filter; /// Creates a test that runs `testdata/repros/Issue{issue}.t.sol`. @@ -133,6 +131,9 @@ test_repro!(3347, false, None, |res| { assert_eq!( decoded, DecodedEvent { + selector: Some(b256!( + "78b9a1f3b55d6797ab2c4537e83ee04ff0c65a1ca1bb39d79a62e0a78d5a8a57" + )), indexed: vec![], body: vec![ DynSolValue::Uint(U256::from(1), 256), @@ -253,7 +254,10 @@ test_repro!(6501, false, None, |res| { let mut res = res.remove("default/repros/Issue6501.t.sol:Issue6501Test").unwrap(); let test = res.test_results.remove("test_hhLogs()").unwrap(); assert_eq!(test.status, TestStatus::Success); - assert_eq!(test.decoded_logs, ["a".to_string(), "1".to_string(), "b 2".to_string()]); + assert_eq!( + decode_console_logs(&test.logs), + ["a".to_string(), "1".to_string(), "b 2".to_string()] + ); let (kind, traces) = test.traces.last().unwrap().clone(); let nodes = traces.into_nodes(); @@ -279,7 +283,7 @@ test_repro!(6501, false, None, |res| { assert_eq!(trace.depth, 1); assert!(trace.success); assert_eq!( - decoded.func, + decoded.call_data, Some(DecodedCallData { signature: expected.0.into(), args: expected.1.into_iter().map(ToOwned::to_owned).collect(), @@ -326,6 +330,10 @@ test_repro!(6634; |config| { config.runner.config = Arc::new(prj_config); }); +// https://github.com/foundry-rs/foundry/issues/7457 +test_repro!(7457); + +// https://github.com/foundry-rs/foundry/issues/7481 test_repro!(7481); // https://github.com/foundry-rs/foundry/issues/5739 @@ -343,3 +351,15 @@ test_repro!(2851, false, None, |res| { // https://github.com/foundry-rs/foundry/issues/8006 test_repro!(8006); + +// https://github.com/foundry-rs/foundry/issues/8277 +test_repro!(8277); + +// https://github.com/foundry-rs/foundry/issues/8287 +test_repro!(8287); + +// https://github.com/foundry-rs/foundry/issues/8168 +test_repro!(8168); + +// https://github.com/foundry-rs/foundry/issues/8383 +test_repro!(8383); diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 26cad2158..0f6e70dc5 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -9,11 +9,12 @@ use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, multi::MultiCompilerLanguage, solc::SolcCompiler, + utils::RuntimeOrHandle, zksync::{ cache::ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME, compile::output::ProjectCompileOutput as ZkProjectCompileOutput, }, - Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, + Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, Vyper, }; use foundry_config::{ fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, @@ -38,6 +39,7 @@ type ZkProject = Project; pub const RE_PATH_SEPARATOR: &str = "/"; const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); +static VYPER: Lazy = Lazy::new(|| std::env::temp_dir().join("vyper")); /// Profile for the tests group. Used to configure separate configurations for test runs. pub enum ForgeTestProfile { @@ -118,6 +120,7 @@ impl ForgeTestProfile { failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), failure_persist_file: Some("testfailure".to_string()), no_zksync_reserved_addresses: false, + show_logs: false, }) .invariant(InvariantConfig { runs: 256, @@ -237,8 +240,10 @@ impl ForgeTestData { /// /// Uses [get_compiled] to lazily compile the project. pub fn new(profile: ForgeTestProfile) -> Self { - let project = profile.project(); - let output = get_compiled(&project); + init_tracing(); + + let mut project = profile.project(); + let output = get_compiled(&mut project); let test_opts = profile.test_opts(&output); let config = profile.config(); let evm_opts = profile.evm_opts(); @@ -247,8 +252,8 @@ impl ForgeTestData { let zk_config = profile.zk_config(); let zk_project = profile.zk_project(); - let project = zk_config.project().expect("failed obtaining project"); - let output = get_compiled(&project); + let mut project = zk_config.project().expect("failed obtaining project"); + let output = get_compiled(&mut project); let zk_output = get_zk_compiled(&zk_project); let layout = ProjectPathsConfig { root: zk_project.paths.root.clone(), @@ -329,7 +334,7 @@ impl ForgeTestData { .enable_isolation(opts.isolate) .sender(sender) .with_test_options(self.test_opts.clone()) - .build(root, output, None, env, opts.clone(), Default::default()) + .build(root, output, None, env, opts, Default::default()) .unwrap() } @@ -363,7 +368,7 @@ impl ForgeTestData { .enable_isolation(opts.isolate) .sender(sender) .with_test_options(test_opts) - .build(root, output, Some(zk_output), env, opts.clone(), dual_compiled_contracts) + .build(root, output, Some(zk_output), env, opts, dual_compiled_contracts) .unwrap() } @@ -400,7 +405,45 @@ impl ForgeTestData { } } -pub fn get_compiled(project: &Project) -> ProjectCompileOutput { +/// Installs Vyper if it's not already present. +pub fn get_vyper() -> Vyper { + if let Ok(vyper) = Vyper::new("vyper") { + return vyper; + } + if let Ok(vyper) = Vyper::new(&*VYPER) { + return vyper; + } + RuntimeOrHandle::new().block_on(async { + #[cfg(target_family = "unix")] + use std::{fs::Permissions, os::unix::fs::PermissionsExt}; + + let suffix = match svm::platform() { + svm::Platform::MacOsAarch64 => "darwin", + svm::Platform::LinuxAmd64 => "linux", + svm::Platform::WindowsAmd64 => "windows.exe", + platform => panic!( + "unsupported platform {platform:?} for installing vyper, \ + install it manually and add it to $PATH" + ), + }; + let url = format!("https://github.com/vyperlang/vyper/releases/download/v0.4.0/vyper.0.4.0+commit.e9db8d9f.{suffix}"); + + let res = reqwest::Client::builder().build().unwrap().get(url).send().await.unwrap(); + + assert!(res.status().is_success()); + + let bytes = res.bytes().await.unwrap(); + + std::fs::write(&*VYPER, bytes).unwrap(); + + #[cfg(target_family = "unix")] + std::fs::set_permissions(&*VYPER, Permissions::from_mode(0o755)).unwrap(); + + Vyper::new(&*VYPER).unwrap() + }) +} + +pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { let lock_file_path = project.sources_path().join(".lock"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. @@ -409,21 +452,27 @@ pub fn get_compiled(project: &Project) -> ProjectCompileOutput { let mut lock = fd_lock::new_lock(&lock_file_path); let read = lock.read().unwrap(); let out; - if project.cache_path().exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { - out = project.compile(); - drop(read); - } else { + + let mut write = None; + if !project.cache_path().exists() || std::fs::read(&lock_file_path).unwrap() != b"1" { drop(read); - let mut write = lock.write().unwrap(); - write.write_all(b"1").unwrap(); - out = project.compile(); - drop(write); + write = Some(lock.write().unwrap()); } - let out = out.unwrap(); + if project.compiler.vyper.is_none() { + project.compiler.vyper = Some(get_vyper()); + } + + out = project.compile().unwrap(); + if out.has_compiler_errors() { panic!("Compiled with errors:\n{out}"); } + + if let Some(ref mut write) = write { + write.write_all(b"1").unwrap(); + } + out } @@ -437,21 +486,24 @@ pub fn get_zk_compiled(zk_project: &ZkProject) -> ZkProjectCompileOutput { let read = lock.read().unwrap(); let out; + let mut write = None; + let zk_compiler = foundry_common::compile::ProjectCompiler::new(); - if zk_project.paths.zksync_cache.exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { - out = zk_compiler.zksync_compile(zk_project, None); + if zk_project.paths.zksync_cache.exists() || std::fs::read(&lock_file_path).unwrap() == b"1" { drop(read); - } else { - drop(read); - let mut write = lock.write().unwrap(); + write = Some(lock.write().unwrap()); + } + + out = zk_compiler.zksync_compile(zk_project, None); + + if let Some(ref mut write) = write { write.write_all(b"1").unwrap(); - out = zk_compiler.zksync_compile(zk_project, None); - drop(write); } - let out = out.expect("failed compiling zksync project"); - if out.has_compiler_errors() { - panic!("Compiled with errors:\n{out}"); + let out: ZkProjectCompileOutput = out.expect("failed compiling zksync project"); + + if let Some(ref mut write) = write { + write.write_all(b"1").unwrap(); } out } diff --git a/crates/forge/tests/it/vyper.rs b/crates/forge/tests/it/vyper.rs new file mode 100644 index 000000000..c40b87541 --- /dev/null +++ b/crates/forge/tests/it/vyper.rs @@ -0,0 +1,10 @@ +//! Integration tests for EVM specifications. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_basic_vyper_test() { + let filter = Filter::new("", "CounterTest", ".*vyper"); + TestConfig::with_filter(TEST_DATA_DEFAULT.runner(), filter).run().await; +} diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs index 25ef8fe19..1ef9c9add 100644 --- a/crates/linking/src/lib.rs +++ b/crates/linking/src/lib.rs @@ -232,7 +232,7 @@ impl<'a> Linker<'a> { let (file, name) = self.convert_artifact_id_to_lib_path(id); for (_, bytecode) in &mut needed_libraries { - bytecode.to_mut().link(file.to_string_lossy(), name.clone(), address); + bytecode.to_mut().link(&file.to_string_lossy(), &name, address); } libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None)); @@ -253,12 +253,12 @@ impl<'a> Linker<'a> { for (name, address) in libs { let address = Address::from_str(address).map_err(LinkerError::InvalidAddress)?; if let Some(bytecode) = contract.bytecode.as_mut() { - bytecode.to_mut().link(file.to_string_lossy(), name, address); + bytecode.to_mut().link(&file.to_string_lossy(), name, address); } if let Some(deployed_bytecode) = contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut()) { - deployed_bytecode.link(file.to_string_lossy(), name, address); + deployed_bytecode.link(&file.to_string_lossy(), name, address); } } } diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs index 3ee0077d9..2522afb2c 100644 --- a/crates/macros/src/console_fmt.rs +++ b/crates/macros/src/console_fmt.rs @@ -10,7 +10,7 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), }; quote! { - impl ::foundry_common::fmt::ConsoleFmt for #name { + impl ConsoleFmt for #name { #tokens } } @@ -19,7 +19,7 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream { fn derive_struct(s: &DataStruct) -> TokenStream { let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); quote! { - fn fmt(&self, _spec: ::foundry_common::fmt::FormatSpec) -> String { + fn fmt(&self, _spec: FormatSpec) -> String { #imp } } @@ -56,12 +56,12 @@ fn impl_struct(s: &DataStruct) -> Option { let first = args.next().unwrap(); let first = first.value(); quote! { - ::foundry_common::fmt::console_format((#first).as_str(), &[#(#args)*]) + console_format((#first).as_str(), &[#(#args)*]) } } else { // console_format("", [...args]) quote! { - ::foundry_common::fmt::console_format("", &[#args]) + console_format("", &[#args]) } }; @@ -92,12 +92,12 @@ fn derive_enum(e: &DataEnum) -> TokenStream { let field = fields.into_iter().next().unwrap(); let fields = Group::new(delimiter, quote!(#field)); quote! { - Self::#name #fields => ::foundry_common::fmt::ConsoleFmt::fmt(#field, spec), + Self::#name #fields => ConsoleFmt::fmt(#field, spec), } }); quote! { - fn fmt(&self, spec: ::foundry_common::fmt::FormatSpec) -> String { + fn fmt(&self, spec: FormatSpec) -> String { match self { #(#arms)* diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index d035e6930..6e11714a8 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -26,7 +26,6 @@ foundry-linking.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true -hex.workspace = true serde.workspace = true eyre.workspace = true serde_json.workspace = true diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index c01c4c52a..634e00bea 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -10,9 +10,7 @@ use alloy_provider::Provider; use eyre::{OptionExt, Result}; use foundry_cheatcodes::ScriptWallets; use foundry_common::{ - compile::{ContractSources, ProjectCompiler}, - provider::try_get_http_provider, - ContractData, ContractsByArtifact, + compile::ProjectCompiler, provider::try_get_http_provider, ContractData, ContractsByArtifact, }; use foundry_compilers::{ artifacts::{BytecodeObject, Libraries}, @@ -22,7 +20,7 @@ use foundry_compilers::{ utils::source_files_iter, ArtifactId, ProjectCompileOutput, }; -use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; +use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::debug::ContractSources}; use foundry_linking::Linker; use foundry_zksync_compiler::DualCompiledContracts; use std::{path::PathBuf, str::FromStr, sync::Arc}; @@ -189,6 +187,7 @@ impl PreprocessedState { } }; + #[allow(clippy::redundant_clone)] let sources_to_compile = source_files_iter( project.paths.sources.as_path(), MultiCompilerLanguage::FILE_EXTENSIONS, @@ -209,7 +208,7 @@ impl PreprocessedState { )?; let sources_to_compile = source_files_iter(project.paths.sources.as_path(), SolcLanguage::FILE_EXTENSIONS) - .chain([target_path.to_path_buf()]); + .chain([target_path.clone()]); let zk_compiler = ProjectCompiler::new().quiet_if(args.opts.silent).files(sources_to_compile); diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index af4111bd0..c865e6f13 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -24,6 +24,7 @@ use foundry_evm::{ decode::decode_console_logs, inspectors::cheatcodes::BroadcastableTransactions, traces::{ + decode_trace_arena, identifier::{SignaturesIdentifier, TraceIdentifiers}, render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, }, @@ -156,7 +157,6 @@ impl PreExecutionState { setup_result.gas_used = script_result.gas_used; setup_result.logs.extend(script_result.logs); setup_result.traces.extend(script_result.traces); - setup_result.debug = script_result.debug; setup_result.labeled_addresses.extend(script_result.labeled_addresses); setup_result.returned = script_result.returned; setup_result.breakpoints = script_result.breakpoints; @@ -431,7 +431,9 @@ impl PreSimulationState { } || !result.success; if should_include { - shell::println(render_trace_arena(trace, decoder).await?)?; + let mut trace = trace.clone(); + decode_trace_arena(&mut trace, decoder).await?; + shell::println(render_trace_arena(&trace))?; } } shell::println(String::new())?; @@ -492,12 +494,18 @@ impl PreSimulationState { Ok(()) } - pub fn run_debugger(&self) -> Result<()> { + pub fn run_debugger(self) -> Result<()> { let mut debugger = Debugger::builder() - .debug_arenas(self.execution_result.debug.as_deref().unwrap_or_default()) + .traces( + self.execution_result + .traces + .into_iter() + .filter(|(t, _)| t.is_execution()) + .collect(), + ) .decoder(&self.execution_artifacts.decoder) - .sources(self.build_data.sources.clone()) - .breakpoints(self.execution_result.breakpoints.clone()) + .sources(self.build_data.sources) + .breakpoints(self.execution_result.breakpoints) .build(); debugger.try_run()?; Ok(()) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 388de5f22..0ad208a26 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -11,7 +11,7 @@ extern crate tracing; use self::transaction::AdditionalContract; use crate::runner::ScriptRunner; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; +use alloy_primitives::{hex, Address, Bytes, Log, TxKind, U256}; use alloy_signer::Signer; use broadcast::next_nonce; use build::PreprocessedState; @@ -37,14 +37,13 @@ use foundry_config::{ use foundry_evm::{ backend::Backend, constants::DEFAULT_CREATE2_DEPLOYER, - debug::DebugArena, executors::ExecutorBuilder, inspectors::{ cheatcodes::{BroadcastableTransactions, ScriptWallets}, CheatsConfig, }, opts::EvmOpts, - traces::Traces, + traces::{TraceMode, Traces}, }; use foundry_wallets::MultiWalletOpts; use foundry_zksync_compiler::DualCompiledContracts; @@ -236,7 +235,7 @@ impl ScriptArgs { .await?; if pre_simulation.args.debug { - pre_simulation.run_debugger()?; + return pre_simulation.run_debugger() } if pre_simulation.args.json { @@ -465,7 +464,6 @@ pub struct ScriptResult { pub success: bool, pub logs: Vec, pub traces: Traces, - pub debug: Option>, pub gas_used: u64, pub labeled_addresses: HashMap, pub transactions: Option, @@ -589,9 +587,12 @@ impl ScriptConfig { // We need to enable tracing to decode contract names: local or external. let mut builder = ExecutorBuilder::new() - .inspectors(|stack| stack.trace(true)) + .inspectors(|stack| { + stack.trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) + }) .spec(self.config.evm_spec_id()) - .gas_limit(self.evm_opts.gas_limit()); + .gas_limit(self.evm_opts.gas_limit()) + .legacy_assertions(self.config.legacy_assertions); let use_zk = self.config.zksync.run_in_zk_mode(); if let Some((known_contracts, script_wallets, target, dual_compiled_contracts)) = @@ -600,7 +601,6 @@ impl ScriptConfig { builder = builder .inspectors(|stack| { stack - .debug(debug) .cheatcodes( CheatsConfig::new( &self.config, diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index a080e2e0d..c0fbd5611 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -38,13 +38,24 @@ pub async fn check_tx_status( return Ok(receipt.into()); } - // If the tx is present in the mempool, run the pending tx future, and - // assume the next drop is really really real - Ok(PendingTransactionBuilder::new(provider, hash) - .with_timeout(Some(Duration::from_secs(120))) - .get_receipt() - .await - .map_or(TxStatus::Dropped, |r| r.into())) + loop { + if let Ok(receipt) = PendingTransactionBuilder::new(provider, hash) + .with_timeout(Some(Duration::from_secs(120))) + .get_receipt() + .await + { + return Ok(receipt.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(TxStatus::Dropped) } .await; diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index aa0af6bb3..300f57809 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -137,8 +137,7 @@ impl ScriptRunner { // Deploy an instance of the contract let DeployResult { address, - raw: - RawCallResult { mut logs, traces: constructor_traces, debug: constructor_debug, .. }, + raw: RawCallResult { mut logs, traces: constructor_traces, .. }, } = self .executor .deploy(CALLER, code, U256::ZERO, None) @@ -147,15 +146,9 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions, debug) = if !setup { - self.executor.backend.set_test_contract(address); - ( - true, - 0, - Default::default(), - Some(library_transactions), - vec![constructor_debug].into_iter().collect(), - ) + let (success, gas_used, labeled_addresses, transactions) = if !setup { + self.executor.backend_mut().set_test_contract(address); + (true, 0, Default::default(), Some(library_transactions)) } else { match self.executor.setup(Some(self.evm_opts.sender), address, None) { Ok(RawCallResult { @@ -163,7 +156,6 @@ impl ScriptRunner { traces: setup_traces, labels, logs: setup_logs, - debug, gas_used, transactions: setup_transactions, .. @@ -175,13 +167,7 @@ impl ScriptRunner { library_transactions.extend(txs); } - ( - !reverted, - gas_used, - labels, - Some(library_transactions), - vec![constructor_debug, debug].into_iter().collect(), - ) + (!reverted, gas_used, labels, Some(library_transactions)) } Err(EvmError::Execution(err)) => { let RawCallResult { @@ -189,7 +175,6 @@ impl ScriptRunner { traces: setup_traces, labels, logs: setup_logs, - debug, gas_used, transactions, .. @@ -201,13 +186,7 @@ impl ScriptRunner { library_transactions.extend(txs); } - ( - !reverted, - gas_used, - labels, - Some(library_transactions), - vec![constructor_debug, debug].into_iter().collect(), - ) + (!reverted, gas_used, labels, Some(library_transactions)) } Err(e) => return Err(e.into()), } @@ -223,7 +202,6 @@ impl ScriptRunner { transactions, logs, traces, - debug, address: None, ..Default::default() }, @@ -258,7 +236,7 @@ impl ScriptRunner { value.unwrap_or(U256::ZERO), None, ); - let (address, RawCallResult { gas_used, logs, traces, debug, .. }) = match res { + let (address, RawCallResult { gas_used, logs, traces, .. }) = match res { Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { let ExecutionErr { raw, reason } = *err; @@ -277,7 +255,6 @@ impl ScriptRunner { traces: traces .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), - debug: debug.map(|debug| vec![debug]), address: Some(address), ..Default::default() }) @@ -313,7 +290,7 @@ impl ScriptRunner { res = self.executor.transact_raw(from, to, calldata, value)?; } - let RawCallResult { result, reverted, logs, traces, labels, debug, transactions, .. } = res; + let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -328,7 +305,6 @@ impl ScriptRunner { vec![(TraceKind::Execution, traces)] }) .unwrap_or_default(), - debug: debug.map(|d| vec![d]), labeled_addresses: labels, transactions, address: None, @@ -352,15 +328,15 @@ impl ScriptRunner { ) -> Result { let mut gas_used = res.gas_used; if matches!(res.exit_reason, return_ok!()) { - // store the current gas limit and reset it later - let init_gas_limit = self.executor.env.tx.gas_limit; + // Store the current gas limit and reset it later. + let init_gas_limit = self.executor.env().tx.gas_limit; let mut highest_gas_limit = gas_used * 3; let mut lowest_gas_limit = gas_used; let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env.tx.gas_limit = mid_gas_limit; + self.executor.env_mut().tx.gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?; match res.exit_reason { InstructionResult::Revert | @@ -385,8 +361,8 @@ impl ScriptRunner { } } } - // reset gas limit in the - self.executor.env.tx.gas_limit = init_gas_limit; + // Reset gas limit in the executor. + self.executor.env_mut().tx.gas_limit = init_gas_limit; } Ok(gas_used) } diff --git a/crates/script/src/sequence.rs b/crates/script/src/sequence.rs index 0eee95951..5a8e789a6 100644 --- a/crates/script/src/sequence.rs +++ b/crates/script/src/sequence.rs @@ -3,10 +3,10 @@ use crate::{ transaction::{AdditionalContract, TransactionWithMetadata}, verify::VerifyBundle, }; -use alloy_primitives::{Address, TxHash}; +use alloy_primitives::{hex, Address, TxHash}; use alloy_rpc_types::{AnyTransactionReceipt, TransactionRequest}; use alloy_serde::WithOtherFields; -use eyre::{ContextCompat, Result, WrapErr}; +use eyre::{eyre, ContextCompat, Result, WrapErr}; use forge_verify::provider::VerificationProviderType; use foundry_cli::utils::{now, Git}; use foundry_common::{fs, shell, SELECTOR_LEN}; @@ -307,9 +307,19 @@ impl ScriptSequence { self.check_unverified(unverifiable_contracts, verify); let num_verifications = future_verifications.len(); - println!("##\nStart verification for ({num_verifications}) contracts",); + let mut num_of_successful_verifications = 0; + println!("##\nStart verification for ({num_verifications}) contracts"); for verification in future_verifications { - verification.await?; + match verification.await { + Ok(_) => { + num_of_successful_verifications += 1; + } + Err(err) => eprintln!("Error during verification: {err:#}"), + } + } + + if num_of_successful_verifications < num_verifications { + return Err(eyre!("Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!")) } println!("All ({num_verifications}) contracts were verified!"); diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index aff08d466..f81725428 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -19,7 +19,7 @@ use eyre::{Context, Result}; use foundry_cheatcodes::{BroadcastableTransactions, ScriptWallets}; use foundry_cli::utils::{has_different_gas_calc, now}; use foundry_common::{get_contract_name, shell, ContractData}; -use foundry_evm::traces::render_trace_arena; +use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ @@ -121,7 +121,7 @@ impl PreSimulationState { // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.env.block.number += U256::from(1); + runner.executor.env_mut().block.number += U256::from(1); } let is_fixed_gas_limit = tx.gas.is_some(); @@ -158,15 +158,13 @@ impl PreSimulationState { let mut abort = false; for res in join_all(futs).await { - let (tx, traces) = res?; + let (tx, mut traces) = res?; // Transaction will be `None`, if execution didn't pass. if tx.is_none() || self.script_config.evm_opts.verbosity > 3 { - for (_, trace) in &traces { - println!( - "{}", - render_trace_arena(trace, &self.execution_artifacts.decoder).await? - ); + for (_, trace) in &mut traces { + decode_trace_arena(trace, &self.execution_artifacts.decoder).await?; + println!("{}", render_trace_arena(trace)); } } diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index 6eb6bf17d..299cc6a3c 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -1,6 +1,6 @@ use super::ScriptResult; use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{Address, Bytes, TxKind, B256}; +use alloy_primitives::{hex, Address, Bytes, TxKind, B256}; use alloy_rpc_types::request::TransactionRequest; use alloy_serde::WithOtherFields; use eyre::{ContextCompat, Result, WrapErr}; @@ -163,7 +163,7 @@ impl TransactionWithMetadata { let constructor_args = &creation_code[bytecode.len()..]; let Some(constructor) = info.abi.constructor() else { return Ok(()) }; - let values = constructor.abi_decode_input(constructor_args, false).map_err(|e| { + let values = constructor.abi_decode_input(constructor_args, false).inspect_err(|_| { error!( contract=?self.contract_name, signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")), @@ -172,7 +172,6 @@ impl TransactionWithMetadata { "Failed to decode constructor arguments", ); debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code)); - e })?; self.arguments = Some(values.iter().map(format_token_raw).collect()); @@ -206,14 +205,13 @@ impl TransactionWithMetadata { if let Some(function) = function { self.function = Some(function.signature()); - let values = function.abi_decode_input(data, false).map_err(|e| { + let values = function.abi_decode_input(data, false).inspect_err(|_| { error!( contract=?self.contract_name, signature=?function, data=hex::encode(data), "Failed to decode function arguments", ); - e })?; self.arguments = Some(values.iter().map(format_token_raw).collect()); } diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index b3bd6c0fb..0de9c9d5a 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -1,6 +1,5 @@ use crate::{build::LinkedBuildData, sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig}; - -use alloy_primitives::Address; +use alloy_primitives::{hex, Address}; use eyre::Result; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml index c4da94041..c83a10152 100644 --- a/crates/sol-macro-gen/Cargo.toml +++ b/crates/sol-macro-gen/Cargo.toml @@ -11,6 +11,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] alloy-json-abi.workspace = true alloy-sol-macro-input.workspace = true diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs index 7867b6673..7309104fe 100644 --- a/crates/sol-macro-gen/src/sol_macro_gen.rs +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -108,24 +108,22 @@ impl MultiSolMacroGen { let cargo_toml_path = bindings_path.join("Cargo.toml"); let mut toml_contents = format!( r#"[package] -name = "{}" -version = "{}" +name = "{name}" +version = "{version}" edition = "2021" [dependencies] -"#, - name, version +"# ); let alloy_dep = if let Some(alloy_version) = alloy_version { format!( - r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{}", features = ["sol-types", "contract"] }}"#, - alloy_version + r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{alloy_version}", features = ["sol-types", "contract"] }}"# ) } else { r#"alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types", "contract"] }"#.to_string() }; - write!(toml_contents, "{}", alloy_dep)?; + write!(toml_contents, "{alloy_dep}")?; fs::write(cargo_toml_path, toml_contents).wrap_err("Failed to write Cargo.toml")?; @@ -146,14 +144,14 @@ edition = "2021" let contents = instance.expansion.as_ref().unwrap().to_string(); if !single_file { - let path = src.join(format!("{}.rs", name)); + let path = src.join(format!("{name}.rs")); let file = syn::parse_file(&contents)?; let contents = prettyplease::unparse(&file); fs::write(path.clone(), contents).wrap_err("Failed to write file")?; - writeln!(&mut lib_contents, "pub mod {};", name)?; + writeln!(&mut lib_contents, "pub mod {name};")?; } else { - write!(&mut lib_contents, "{}", contents)?; + write!(&mut lib_contents, "{contents}")?; } } @@ -196,13 +194,13 @@ edition = "2021" let file = syn::parse_file(&contents)?; let contents = prettyplease::unparse(&file); - fs::write(bindings_path.join(format!("{}.rs", name)), contents) + fs::write(bindings_path.join(format!("{name}.rs")), contents) .wrap_err("Failed to write file")?; } else { // Single File let mut contents = String::new(); write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; - write!(mod_contents, "{}", contents)?; + write!(mod_contents, "{contents}")?; } } @@ -249,9 +247,9 @@ edition = "2021" for instance in &self.instances { let name = instance.name.to_lowercase(); let path = if is_mod { - crate_path.join(format!("{}.rs", name)) + crate_path.join(format!("{name}.rs")) } else { - crate_path.join(format!("src/{}.rs", name)) + crate_path.join(format!("src/{name}.rs")) }; let tokens = instance .expansion @@ -263,9 +261,8 @@ edition = "2021" write!( &mut super_contents, - r#"pub mod {}; - "#, - name + r#"pub mod {name}; + "# )?; } diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 95ef8d6c9..e3cb1f542 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -30,9 +30,10 @@ similar-asserts.workspace = true regex = "1" serde_json.workspace = true tracing.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } walkdir.workspace = true rand.workspace = true +snapbox = { version = "0.6.9", features = ["json"] } [features] # feature for integration tests that test external projects diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 28bb4def1..2a0093278 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -28,6 +28,8 @@ pub use script::{ScriptOutcome, ScriptTester}; // re-exports for convenience pub use foundry_compilers; +pub use snapbox::{assert_data_eq, file, str}; + /// Initializes tracing for tests. pub fn init_tracing() { let _ = tracing_subscriber::FmtSubscriber::builder() diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 63a701a4f..8ed6fb8d5 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -60,6 +60,9 @@ static ETHERSCAN_MAINNET_KEYS: Lazy> = Lazy::new(|| { "4FYHTY429IXYMJNS4TITKDMUKW5QRYDX61", "QYKNT5RHASZ7PGQE68FNQWH99IXVTVVD2I", "VXMQ117UN58Y4RHWUB8K1UGCEA7UQEWK55", + "C7I2G4JTA5EPYS42Z8IZFEIMQNI5GXIJEV", + "A15KZUMZXXCK1P25Y1VP1WGIVBBHIZDS74", + "3IA6ASNQXN8WKN7PNFX7T72S9YG56X9FPG", ]; keys.shuffle(&mut rand::thread_rng()); diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 6d2d5233a..c1c2df0b0 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,17 +1,18 @@ use crate::init_tracing; use eyre::{Result, WrapErr}; use foundry_compilers::{ - artifacts::Settings, cache::CompilerCache, compilers::multi::MultiCompiler, error::Result as SolcResult, project_util::{copy_dir, TempProject}, + solc::SolcSettings, ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, }; use foundry_config::Config; use once_cell::sync::Lazy; use parking_lot::Mutex; use regex::Regex; +use snapbox::cmd::OutputAssert; use std::{ env, ffi::OsStr, @@ -27,6 +28,9 @@ use std::{ static CURRENT_DIR_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); +/// The commit of forge-std to use. +const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); + /// Stores whether `stdout` is a tty / terminal. pub static IS_TTY: Lazy = Lazy::new(|| std::io::stdout().is_terminal()); @@ -192,7 +196,7 @@ impl ExtTester { // Run the tests. test_cmd.arg("test"); test_cmd.args(&self.args); - test_cmd.args(["--fuzz-runs=32", "--ffi", "-vvvvv"]); + test_cmd.args(["--fuzz-runs=32", "--ffi", "-vvv"]); test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); if let Some(fork_block) = self.fork_block { @@ -250,6 +254,14 @@ pub fn initialize(target: &Path) { eprintln!("- initializing template dir in {}", prj.root().display()); cmd.args(["init", "--force"]).assert_success(); + // checkout forge-std + assert!(Command::new("git") + .current_dir(prj.root().join("lib/forge-std")) + .args(["checkout", FORGE_STD_REVISION]) + .output() + .expect("failed to checkout forge-std") + .status + .success()); cmd.forge_fuse().args(["build", "--use", SOLC_VERSION]).assert_success(); // Remove the existing template, if any. @@ -535,7 +547,7 @@ impl TestProject { #[track_caller] pub fn assert_create_dirs_exists(&self) { self.paths().create_all().unwrap_or_else(|_| panic!("Failed to create project paths")); - CompilerCache::::default() + CompilerCache::::default() .write(&self.paths().cache) .expect("Failed to create cache"); self.assert_all_paths_exist(); @@ -910,8 +922,8 @@ impl TestCommand { /// Runs the command and asserts that it resulted in success #[track_caller] - pub fn assert_success(&mut self) { - self.output(); + pub fn assert_success(&mut self) -> OutputAssert { + self.assert().success() } /// Executes command, applies stdin function and returns output @@ -1016,7 +1028,7 @@ impl TestCommand { eyre::eyre!("{}", self.make_error_message(out, expected_fail)) } - fn make_error_message(&self, out: &Output, expected_fail: bool) -> String { + pub fn make_error_message(&self, out: &Output, expected_fail: bool) -> String { let msg = if expected_fail { "expected failure but command succeeded!" } else { @@ -1044,6 +1056,10 @@ stderr: lossy_string(&out.stderr), ) } + + pub fn assert(&mut self) -> OutputAssert { + OutputAssert::new(self.execute()) + } } /// Extension trait for [`Output`]. @@ -1060,6 +1076,12 @@ pub trait OutputExt { /// Ensure the command wrote the expected data to `stderr`. fn stderr_matches_path(&self, expected_path: impl AsRef); + + /// Returns the stderr as lossy string + fn stderr_lossy(&self) -> String; + + /// Returns the stdout as lossy string + fn stdout_lossy(&self) -> String; } /// Patterns to remove from fixtures before comparing output @@ -1107,6 +1129,14 @@ impl OutputExt for Output { let err = lossy_string(&self.stderr); similar_asserts::assert_eq!(normalize_output(&err), normalize_output(&expected)); } + + fn stderr_lossy(&self) -> String { + lossy_string(&self.stderr) + } + + fn stdout_lossy(&self) -> String { + lossy_string(&self.stdout) + } } /// Returns the fixture path depending on whether the current terminal is tty diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 29cd75770..8038746d7 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -19,7 +19,6 @@ foundry-cli.workspace = true foundry-common.workspace = true foundry-evm.workspace = true serde_json.workspace = true -hex.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-rpc-types.workspace = true diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 44b1f5542..dfa8c31a9 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -179,6 +179,7 @@ impl VerifyBytecodeArgs { // Get creation tx hash let creation_data = etherscan.contract_creation_data(self.address).await?; + trace!(creation_tx_hash = ?creation_data.transaction_hash); let mut transaction = provider .get_transaction_by_hash(creation_data.transaction_hash) .await @@ -189,7 +190,7 @@ impl VerifyBytecodeArgs { let receipt = provider .get_transaction_receipt(creation_data.transaction_hash) .await - .or_else(|e| eyre::bail!("Couldn't fetch transacrion receipt from RPC: {:?}", e))?; + .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?; let receipt = if let Some(receipt) = receipt { receipt @@ -199,17 +200,19 @@ impl VerifyBytecodeArgs { creation_data.transaction_hash ); }; + // Extract creation code - let maybe_creation_code = if receipt.contract_address == Some(self.address) { - &transaction.input - } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { - &transaction.input[32..] - } else { - eyre::bail!( - "Could not extract the creation code for contract at address {}", - self.address - ); - }; + let maybe_creation_code = + if receipt.to.is_none() && receipt.contract_address == Some(self.address) { + &transaction.input + } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) { + &transaction.input[32..] + } else { + eyre::bail!( + "Could not extract the creation code for contract at address {}", + self.address + ); + }; // If bytecode_hash is disabled then its always partial verification let (verification_type, has_metadata) = @@ -235,6 +238,7 @@ impl VerifyBytecodeArgs { }; // Append constructor args to the local_bytecode + trace!(%constructor_args); let mut local_bytecode_vec = local_bytecode.to_vec(); local_bytecode_vec.extend_from_slice(&constructor_args); @@ -257,6 +261,21 @@ impl VerifyBytecodeArgs { &config, ); + // If the creation code does not match, the runtime also won't match. Hence return. + if !did_match { + self.print_result( + (did_match, with_status), + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + if self.json { + println!("{}", serde_json::to_string(&json_results)?); + } + return Ok(()); + } + // Get contract creation block let simulation_block = match self.block { Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, @@ -284,7 +303,7 @@ impl VerifyBytecodeArgs { TracingExecutor::get_fork_material(&fork_config, evm_opts).await?; let mut executor = - TracingExecutor::new(env.clone(), fork, Some(fork_config.evm_version), false); + TracingExecutor::new(env.clone(), fork, Some(fork_config.evm_version), false, false); env.block.number = U256::from(simulation_block); let block = provider.get_block(simulation_block.into(), true.into()).await?; @@ -306,6 +325,20 @@ impl VerifyBytecodeArgs { env.block.gas_limit = U256::from(block.header.gas_limit); } + // Replace the `input` with local creation code in the creation tx. + if let Some(to) = transaction.to { + if to == DEFAULT_CREATE2_DEPLOYER { + let mut input = transaction.input[..32].to_vec(); // Salt + input.extend_from_slice(&local_bytecode_vec); + transaction.input = Bytes::from(input); + + // Deploy default CREATE2 deployer + executor.deploy_create2_deployer()?; + } + } else { + transaction.input = Bytes::from(local_bytecode_vec); + } + configure_tx_env(&mut env, &transaction); let env_with_handler = @@ -329,7 +362,7 @@ impl VerifyBytecodeArgs { // State commited using deploy_with_env, now get the runtime bytecode from the db. let fork_runtime_code = executor - .backend + .backend_mut() .basic(contract_address)? .ok_or_else(|| { eyre::eyre!( @@ -348,9 +381,9 @@ impl VerifyBytecodeArgs { let onchain_runtime_code = provider.get_code_at(self.address).block_id(BlockId::number(simulation_block)).await?; - // Compare the runtime bytecode with the locally built bytecode + // Compare the onchain runtime bytecode with the runtime code from the fork. let (did_match, with_status) = try_match( - fork_runtime_code.bytecode(), + &fork_runtime_code.original_bytes(), &onchain_runtime_code, &constructor_args, &verification_type, @@ -489,7 +522,7 @@ impl VerifyBytecodeArgs { let json_res = JsonResult { bytecode_type, matched: false, - verification_type: self.verification_type, + verification_type: res.1.unwrap(), message: Some(format!( "{bytecode_type:?} code did not match - this may be due to varying compiler settings" )), @@ -567,11 +600,11 @@ fn try_match( has_metadata: bool, ) -> Result<(bool, Option)> { // 1. Try full match - if *match_type == VerificationType::Full && local_bytecode.starts_with(bytecode) { + if *match_type == VerificationType::Full && local_bytecode == bytecode { Ok((true, Some(VerificationType::Full))) } else { try_partial_match(local_bytecode, bytecode, constructor_args, is_runtime, has_metadata) - .map(|matched| (matched, matched.then_some(VerificationType::Partial))) + .map(|matched| (matched, Some(VerificationType::Partial))) } } @@ -583,37 +616,30 @@ fn try_partial_match( has_metadata: bool, ) -> Result { // 1. Check length of constructor args - if constructor_args.is_empty() { + if constructor_args.is_empty() || is_runtime { // Assume metadata is at the end of the bytecode - if has_metadata { - local_bytecode = extract_metadata_hash(local_bytecode)?; - bytecode = extract_metadata_hash(bytecode)?; - } - - // Now compare the creation code and bytecode - return Ok(local_bytecode.starts_with(bytecode)); - } - - if is_runtime { - if has_metadata { - local_bytecode = extract_metadata_hash(local_bytecode)?; - bytecode = extract_metadata_hash(bytecode)?; - } - - // Now compare the local code and bytecode - return Ok(local_bytecode.starts_with(bytecode)); + return try_extract_and_compare_bytecode(local_bytecode, bytecode, has_metadata) } // If not runtime, extract constructor args from the end of the bytecode bytecode = &bytecode[..bytecode.len() - constructor_args.len()]; local_bytecode = &local_bytecode[..local_bytecode.len() - constructor_args.len()]; + try_extract_and_compare_bytecode(local_bytecode, bytecode, has_metadata) +} + +fn try_extract_and_compare_bytecode( + mut local_bytecode: &[u8], + mut bytecode: &[u8], + has_metadata: bool, +) -> Result { if has_metadata { local_bytecode = extract_metadata_hash(local_bytecode)?; bytecode = extract_metadata_hash(bytecode)?; } - Ok(local_bytecode.starts_with(bytecode)) + // Now compare the local code and bytecode + Ok(local_bytecode == bytecode) } /// @dev This assumes that the metadata is at the end of the bytecode diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index 785643315..d1f82d111 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -3,7 +3,7 @@ use crate::provider::VerificationContext; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; use foundry_compilers::{ - artifacts::{BytecodeHash, Source}, + artifacts::{BytecodeHash, Source, Sources}, buildinfo::RawBuildInfo, compilers::{ solc::{SolcCompiler, SolcLanguage, SolcVersionedInput}, @@ -20,8 +20,7 @@ use foundry_compilers::{ AggregatedCompilerOutput, }; use semver::{BuildMetadata, Version}; - -use std::{collections::BTreeMap, path::Path}; +use std::path::Path; #[derive(Debug)] pub struct EtherscanFlattenedSource; @@ -130,7 +129,7 @@ impl EtherscanFlattenedSource { let solc = Solc::find_or_install(&version)?; let input = SolcVersionedInput::build( - BTreeMap::from([("contract.sol".into(), Source::new(content))]), + Sources::from([("contract.sol".into(), Source::new(content))]), Default::default(), SolcLanguage::Solidity, version.clone(), @@ -139,7 +138,7 @@ impl EtherscanFlattenedSource { let out = SolcCompiler::Specific(solc).compile(&input)?; if out.errors.iter().any(|e| e.is_error()) { let mut o = AggregatedCompilerOutput::::default(); - o.extend(version.clone(), RawBuildInfo::new(&input, &out, false)?, out); + o.extend(version, RawBuildInfo::new(&input, &out, false)?, out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( @@ -179,10 +178,10 @@ Diagnostics: {diags}", let zksolc = ZkSolc::find_installed_version(&version)? .unwrap_or(ZkSolc::blocking_install(&version)?); - let mut input = ZkSolcVersionedInput { + let input = ZkSolcVersionedInput { input: ZkSolcInput { language: SolcLanguage::Solidity, - sources: BTreeMap::from([("contract.sol".into(), Source::new(content))]), + sources: Sources::from([("contract.sol".into(), Source::new(content))]), ..Default::default() }, solc_version: version.clone(), @@ -191,10 +190,10 @@ Diagnostics: {diags}", include_paths: Default::default(), }; - let out = zksolc.compile(&mut input)?; + let out = zksolc.compile(&input)?; if out.has_error() { let mut o = ZkAggregatedCompilerOutput::default(); - o.extend(version.clone(), raw_build_info_new(&input, &out, false)?, out); + o.extend(version, raw_build_info_new(&input, &out, false)?, out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 7b1520fd6..17bbe687c 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -1,6 +1,7 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; use crate::{provider::VerificationContext, retry::RETRY_CHECK_ON_VERIFY}; use alloy_json_abi::Function; +use alloy_primitives::hex; use alloy_provider::Provider; use eyre::{eyre, Context, OptionExt, Result}; use foundry_block_explorers::{ @@ -10,7 +11,11 @@ use foundry_block_explorers::{ Client, }; use foundry_cli::utils::{self, read_constructor_args_file, LoadConfig}; -use foundry_common::{abi::encode_function_args, retry::Retry, shell}; +use foundry_common::{ + abi::encode_function_args, + retry::{Retry, RetryError}, + shell, +}; use foundry_compilers::{artifacts::BytecodeObject, solc::Solc, Artifact}; use foundry_config::{Chain, Config}; use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; @@ -18,7 +23,6 @@ use futures::FutureExt; use once_cell::sync::Lazy; use regex::Regex; use semver::{BuildMetadata, Version}; - use std::fmt::Debug; mod flatten; @@ -156,12 +160,13 @@ impl VerificationProvider for EtherscanVerificationProvider { )?; let retry: Retry = args.retry.into(); retry - .run_async(|| { + .run_async_until_break(|| { async { let resp = etherscan .check_contract_verification_status(args.id.clone()) .await - .wrap_err("Failed to request verification status")?; + .wrap_err("Failed to request verification status") + .map_err(RetryError::Retry)?; trace!(target: "forge::verify", ?resp, "Received verification response"); @@ -171,15 +176,15 @@ impl VerificationProvider for EtherscanVerificationProvider { ); if resp.result == "Pending in queue" { - return Err(eyre!("Verification is still pending...",)) + return Err(RetryError::Retry(eyre!("Verification is still pending...",))) } if resp.result == "In progress" { - return Err(eyre!("Verification is in progress...",)) + return Err(RetryError::Retry(eyre!("Verification is in progress...",))) } if resp.result == "Unable to verify" { - return Err(eyre!("Unable to verify.",)) + return Err(RetryError::Retry(eyre!("Unable to verify.",))) } if resp.result == "Already Verified" { @@ -188,8 +193,7 @@ impl VerificationProvider for EtherscanVerificationProvider { } if resp.status == "0" { - println!("Contract failed to verify."); - std::process::exit(1); + return Err(RetryError::Break(eyre!("Contract failed to verify.",))) } if resp.result == "Pass - Verified" { @@ -201,7 +205,7 @@ impl VerificationProvider for EtherscanVerificationProvider { .boxed() }) .await - .wrap_err("Checking verification result failed:") + .wrap_err("Checking verification result failed") } } @@ -463,7 +467,7 @@ impl EtherscanVerificationProvider { let output = context.project.compile_file(&context.target_path)?; let artifact = output - .find(context.target_path.to_string_lossy(), &context.target_name) + .find(&context.target_path, &context.target_name) .ok_or_eyre("Contract artifact wasn't found locally")?; let bytecode = artifact .get_bytecode_object() diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index b93ecdf22..daa4c8dd9 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -2,7 +2,7 @@ use super::{EtherscanSourceProvider, VerifyArgs}; use crate::provider::VerificationContext; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; -use foundry_compilers::artifacts::StandardJsonCompilerInput; +use foundry_compilers::{artifacts::StandardJsonCompilerInput, solc::SolcLanguage}; #[derive(Debug)] pub struct EtherscanStandardJsonSource; @@ -29,7 +29,7 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { .collect(); // remove all incompatible settings - input.settings.sanitize(&context.compiler_version); + input.settings.sanitize(&context.compiler_version, SolcLanguage::Solidity); let source = serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 12dfb23f6..46ca0b944 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -55,7 +55,7 @@ impl VerificationContext { .compile(&project)?; let artifact = output - .find(self.target_path.to_string_lossy(), &self.target_name) + .find(&self.target_path, &self.target_name) .ok_or_eyre("failed to find target artifact when compiling for abi")?; artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") @@ -74,7 +74,7 @@ impl VerificationContext { .compile(&project)?; let artifact = output - .find(self.target_path.to_string_lossy(), &self.target_name) + .find(&self.target_path, &self.target_name) .ok_or_eyre("failed to find target artifact when compiling for metadata")?; artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI") diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index 183a97854..f0e3ddc11 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -41,7 +41,6 @@ async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } derive_builder = "0.20.0" eyre.workspace = true -hex = { workspace = true, features = ["serde"] } rpassword = "7" serde.workspace = true thiserror.workspace = true diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index 4e299b055..a5ee5ec1c 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -1,8 +1,8 @@ +use alloy_primitives::hex::FromHexError; use alloy_signer::k256::ecdsa; use alloy_signer_ledger::LedgerError; use alloy_signer_local::LocalSignerError; use alloy_signer_trezor::TrezorError; -use hex::FromHexError; #[cfg(feature = "aws-kms")] use alloy_signer_aws::AwsSignerError; diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index da19b6d9e..ab1871d6b 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -1,11 +1,10 @@ use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; -use alloy_primitives::B256; +use alloy_primitives::{hex::FromHex, B256}; use alloy_signer_ledger::HDPath as LedgerHDPath; use alloy_signer_local::PrivateKeySigner; use alloy_signer_trezor::HDPath as TrezorHDPath; use eyre::{Context, Result}; use foundry_config::Config; -use hex::FromHex; use std::{ fs, path::{Path, PathBuf}, @@ -148,3 +147,17 @@ pub fn create_keystore_signer( Ok((None, Some(PendingSigner::Keystore(path.clone())))) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_private_key_signer() { + let pk = B256::random(); + let pk_str = pk.to_string(); + assert!(create_private_key_signer(&pk_str).is_ok()); + // skip 0x + assert!(create_private_key_signer(&pk_str[2..]).is_ok()); + } +} diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index f1f7bad88..75441f683 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -2,7 +2,7 @@ use crate::error::WalletSignerError; use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::TxSigner; -use alloy_primitives::{Address, ChainId, B256}; +use alloy_primitives::{hex, Address, ChainId, B256}; use alloy_signer::{Signature, Signer}; use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}; diff --git a/crates/zksync/compiler/src/lib.rs b/crates/zksync/compiler/src/lib.rs index a5e6f095e..f8c7263d5 100644 --- a/crates/zksync/compiler/src/lib.rs +++ b/crates/zksync/compiler/src/lib.rs @@ -14,8 +14,12 @@ pub use zksolc::*; pub mod libraries; use foundry_compilers::{ - artifacts::Severity, error::SolcError, solc::SolcCompiler, zksolc::ZkSolc, - zksync::config::ZkSolcConfig, Compiler, Project, ProjectBuilder, + artifacts::Severity, + error::SolcError, + solc::{SolcCompiler, SolcSettings}, + zksolc::ZkSolc, + zksync::config::ZkSolcConfig, + Compiler, Project, ProjectBuilder, }; /// Ensures that the configured version is installed if explicitly set @@ -122,9 +126,9 @@ pub fn standard_json_input( target_path: impl AsRef, ) -> Result where - C::Settings: Into, + C::Settings: Into, { - let mut input = project.standard_json_input(target_path)?; + let mut input = project.standard_json_input(target_path.as_ref())?; tracing::debug!(?input.settings.remappings, "standard_json_input for zksync"); let mut settings = project.zksync_zksolc_config.settings.clone(); diff --git a/crates/zksync/core/Cargo.toml b/crates/zksync/core/Cargo.toml index 2b10142f3..faacb2a2d 100644 --- a/crates/zksync/core/Cargo.toml +++ b/crates/zksync/core/Cargo.toml @@ -13,6 +13,7 @@ exclude.workspace = true [dependencies] foundry-common.workspace = true +foundry-evm-abi.workspace = true foundry-cheatcodes-common.workspace = true foundry-zksync-compiler.workspace = true alloy-primitives.workspace = true @@ -25,7 +26,6 @@ alloy-provider.workspace = true alloy-transport.workspace = true alloy-rpc-types.workspace = true alloy-consensus.workspace = true -hex.workspace = true itertools.workspace = true revm = { workspace = true, default-features = false, features = [ "std", @@ -50,7 +50,7 @@ zksync_utils.workspace = true zksync_contracts.workspace = true zksync_state.workspace = true -ansi_term = "0.12.1" +ansiterm = "0.12.2" once_cell = "1" eyre = "0.6" url = "2" diff --git a/crates/zksync/core/src/cheatcodes.rs b/crates/zksync/core/src/cheatcodes.rs index fc55cac3d..25aee6f5d 100644 --- a/crates/zksync/core/src/cheatcodes.rs +++ b/crates/zksync/core/src/cheatcodes.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use alloy_primitives::{Bytes, B256}; +use alloy_primitives::{hex, Bytes, B256}; use revm::{ primitives::{Address, Bytecode, U256 as rU256}, Database, InnerEvmContext, diff --git a/crates/zksync/core/src/utils.rs b/crates/zksync/core/src/utils.rs index 6d3065342..0b50544a9 100644 --- a/crates/zksync/core/src/utils.rs +++ b/crates/zksync/core/src/utils.rs @@ -1,5 +1,6 @@ //! The `zk_utils` module provides utility functions specifically designed for interacting with +use alloy_primitives::hex; /// zkSync, an Ethereum layer 2 scaling solution. /// /// This module encapsulates various functionalities related to zkSync, including retrieving diff --git a/crates/zksync/core/src/vm/db.rs b/crates/zksync/core/src/vm/db.rs index a8da56688..8b8f92bf7 100644 --- a/crates/zksync/core/src/vm/db.rs +++ b/crates/zksync/core/src/vm/db.rs @@ -8,7 +8,7 @@ use std::{collections::HashMap, fmt::Debug}; use alloy_primitives::{Address, U256 as rU256}; use foundry_cheatcodes_common::record::RecordAccess; -use revm::{primitives::Account, Database, EvmContext}; +use revm::{primitives::Account, Database, EvmContext, InnerEvmContext}; use zksync_basic_types::{L2ChainId, H160, H256, U256}; use zksync_state::ReadStorage; use zksync_types::{ @@ -27,7 +27,7 @@ pub(crate) const DEFAULT_CHAIN_ID: u32 = 31337; pub struct ZKVMData<'a, DB: Database> { // pub db: &'a mut DB, // pub journaled_state: &'a mut JournaledState, - ecx: &'a mut EvmContext, + ecx: &'a mut InnerEvmContext, pub factory_deps: HashMap>, pub override_keys: HashMap, pub accesses: Option<&'a mut RecordAccess>, @@ -53,7 +53,7 @@ where ::Error: Debug, { /// Create a new instance of [ZKEVMData]. - pub fn new(ecx: &'a mut EvmContext) -> Self { + pub fn new(ecx: &'a mut InnerEvmContext) -> Self { // load all deployed contract bytecodes from the JournaledState as factory deps let mut factory_deps = ecx .journaled_state diff --git a/crates/zksync/core/src/vm/farcall.rs b/crates/zksync/core/src/vm/farcall.rs index c9d05d2f4..5766eb3a1 100644 --- a/crates/zksync/core/src/vm/farcall.rs +++ b/crates/zksync/core/src/vm/farcall.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, default, fmt::Debug}; -use alloy_primitives::Address; +use alloy_primitives::{hex, Address}; use itertools::Itertools; use multivm::{ vm_1_3_2::zk_evm_1_3_3::zkevm_opcode_defs::RetABI, diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index e3207c5d2..f433c2731 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -1,11 +1,10 @@ -use alloy_primitives::Log; +use alloy_primitives::{hex, Log}; use era_test_node::{ formatter, node::ShowCalls, system_contracts::{Options, SystemContracts}, utils::bytecode_to_factory_dep, }; -use foundry_common::{Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS}; use itertools::Itertools; use multivm::{ interface::{Halt, VmInterface, VmRevertReason}, @@ -46,6 +45,9 @@ use crate::{ tracer::{CallContext, CheatcodeTracer, CheatcodeTracerContext}, }, }; +use foundry_evm_abi::{ + patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS, +}; /// Maximum gas price allowed for L1. const MAX_L1_GAS_PRICE: u64 = 1000; @@ -404,7 +406,6 @@ fn inspect_inner( value: log.data.data.to_vec(), }); } - let resolve_hashes = get_env_var::("ZK_DEBUG_RESOLVE_HASHES"); let show_outputs = get_env_var::("ZK_DEBUG_SHOW_OUTPUTS"); info!("=== Calls: "); @@ -472,7 +473,7 @@ impl ConsoleLogParser { let mut input = current_call.input.clone(); // Patch the Hardhat-style selector (`uint` instead of `uint256`) - foundry_common::patch_hh_console_selector(&mut input); + patch_hh_console_selector(&mut input); // Decode the call let Ok(call) = HardhatConsole::HardhatConsoleCalls::abi_decode(&input, false) else { @@ -491,7 +492,7 @@ impl ConsoleLogParser { logs.push(log); if print { - info!("{}", ansi_term::Color::Cyan.paint(message)); + info!("{}", ansiterm::Color::Cyan.paint(message)); } } } diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 368d9c8b9..57f959b8a 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -1,9 +1,10 @@ +use alloy_primitives::hex; use foundry_zksync_compiler::DualCompiledContract; use itertools::Itertools; use revm::{ interpreter::{CallInputs, CallScheme, CallValue, CreateInputs}, primitives::{Address, CreateScheme, Env, ResultAndState, TransactTo, B256, U256 as rU256}, - Database, EvmContext, + Database, EvmContext, InnerEvmContext, }; use tracing::{debug, error, info}; use zksync_basic_types::H256; @@ -107,7 +108,7 @@ where } /// Retrieves nonce for a given address. -pub fn nonce(address: Address, ecx: &mut EvmContext) -> u32 +pub fn nonce(address: Address, ecx: &mut InnerEvmContext) -> u32 where DB: Database, ::Error: Debug, @@ -278,11 +279,12 @@ pub fn encode_create_params( fn get_historical_block_hashes(ecx: &mut EvmContext) -> HashMap { let mut block_hashes = HashMap::default(); for i in 1..=256u32 { - let (block_number, overflow) = ecx.env.block.number.overflowing_sub(rU256::from(i)); + let (block_number, overflow) = + ecx.env.block.number.overflowing_sub(alloy_primitives::U256::from(i)); if overflow { break } - match ecx.block_hash(block_number) { + match ecx.block_hash(block_number.to_u256().as_u64()) { Ok(block_hash) => { block_hashes.insert(block_number, block_hash); } diff --git a/crates/zksync/core/src/vm/tracer.rs b/crates/zksync/core/src/vm/tracer.rs index 0ce2d0cf2..0b765a539 100644 --- a/crates/zksync/core/src/vm/tracer.rs +++ b/crates/zksync/core/src/vm/tracer.rs @@ -147,7 +147,7 @@ impl CheatcodeTracer { fn has_empty_code(&self, storage: StoragePtr, target: Address) -> bool { // The following addresses are expected to have empty bytecode let ignored_known_addresses = - [foundry_common::HARDHAT_CONSOLE_ADDRESS, self.call_context.tx_caller]; + [foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS, self.call_context.tx_caller]; let contract_code = storage.borrow_mut().read_value(&get_code_key(&target.to_h160())); diff --git a/deny.toml b/deny.toml index 5589f4c5d..10624b4ae 100644 --- a/deny.toml +++ b/deny.toml @@ -2,14 +2,15 @@ # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] -vulnerability = "deny" -unmaintained = "warn" +version = 2 yanked = "warn" -notice = "warn" ignore = [ # https://github.com/watchexec/watchexec/issues/852 "RUSTSEC-2024-0350", "RUSTSEC-2024-0351", + "RUSTSEC-2022-0041", + "RUSTSEC-2023-0045", + "RUSTSEC-2020-0016", ] # This section is considered when running `cargo deny check bans`. @@ -17,7 +18,7 @@ ignore = [ # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" +multiple-versions = "allow" # Lint level for when a crate version requirement is `*` wildcards = "allow" highlight = "all" @@ -54,6 +55,7 @@ allow = [ "0BSD", "WTFPL", "Unicode-3.0", + "MPL-2.0", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses @@ -63,12 +65,13 @@ exceptions = [ # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal { allow = ["CC0-1.0"], name = "tiny-keccak" }, - { allow = ["CC0-1.0"], name = "to_method" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, { allow = ["CC0-1.0"], name = "dunce" }, { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, { allow = ["CC0-1.0"], name = "constant_time_eq" }, + { allow = ["CC0-1.0"], name = "secp256k1" }, + { allow = ["CC0-1.0"], name = "secp256k1-sys" }, ] #copyleft = "deny" @@ -101,6 +104,7 @@ allow-git = [ "https://github.com/bluealloy/revm", "https://github.com/lambdaclass/zksync-web3-rs", "https://github.com/Moonsong-Labs/compilers", + "https://github.com/Moonsong-Labs/foundry-zksync-fork-db", "https://github.com/Moonsong-Labs/block-explorers", "https://github.com/RustCrypto/hashes", ] diff --git a/flake.lock b/flake.lock index 9ad80af8b..45ed6bb43 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711655175, - "narHash": "sha256-1xiaYhC3ul4y+i3eicYxeERk8ZkrNjLkrFSb/UW36Zw=", + "lastModified": 1719468428, + "narHash": "sha256-vN5xJAZ4UGREEglh3lfbbkIj+MPEYMuqewMn4atZFaQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "64c81edb4b97a51c5bbc54c191763ac71a6517ee", + "rev": "1e3deb3d8a86a870d925760db1a5adecc64d329d", "type": "github" }, "original": { @@ -44,19 +44,16 @@ }, "rust-overlay": { "inputs": { - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1711678273, - "narHash": "sha256-7lIB0hMRnfzx/9oSIwTnwXmVnbvVGRoadOCW+1HI5zY=", + "lastModified": 1719714047, + "narHash": "sha256-MeNPopLLv63EZj5L43j4TZkmW4wj1ouoc/h/E20sl/U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "42a168449605950935f15ea546f6f770e5f7f629", + "rev": "cb216719ce89a43dfb3d1b86a9575e89f4b727a4", "type": "github" }, "original": { @@ -72,14 +69,15 @@ ], "nixpkgs": [ "nixpkgs" - ] + ], + "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" }, "locked": { - "lastModified": 1711538161, - "narHash": "sha256-rETVdEIQ2PyEcNgzXXFSiYAYl0koCeGDIWp9XYBTxoQ=", + "lastModified": 1717442267, + "narHash": "sha256-6TnQvA6Q/xC3r1M+wGC5gnDc/5XfOPjC8X6LlGDWDNc=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "a995838545a7383a0b37776e969743b1346d5479", + "rev": "2ac2862f224aa0d67cbc6b3246392489f8a50596", "type": "github" }, "original": { @@ -88,6 +86,18 @@ "type": "github" } }, + "solc-macos-amd64-list-json": { + "flake": false, + "locked": { + "narHash": "sha256-Prwz95BgMHcWd72VwVbcH17LsV9f24K2QMcUiWUQZzI=", + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/f743ca7/macosx-amd64/list.json" + }, + "original": { + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/f743ca7/macosx-amd64/list.json" + } + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 46ddb920c..ff783b495 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,6 @@ url = "github:oxalica/rust-overlay"; inputs = { nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; }; }; solc = { @@ -21,7 +20,6 @@ outputs = { self, nixpkgs, rust-overlay, flake-utils, solc }: flake-utils.lib.eachDefaultSystem (system: let - overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default solc.overlay ]; @@ -35,17 +33,15 @@ devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ pkg-config - libusb1 - ] ++ lib.optionals pkgs.stdenv.isDarwin [ - pkgs.darwin.apple_sdk.frameworks.AppKit - ]; - buildInputs = [ - pkgs.rust-analyzer-unwrapped + solc_0_8_23 + (solc.mkDefault pkgs solc_0_8_23) toolchain ]; + buildInputs = lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.apple_sdk.frameworks.AppKit + ]; packages = with pkgs; [ - solc_0_8_20 - (solc.mkDefault pkgs solc_0_8_20) + rust-analyzer-unwrapped ]; # Environment variables diff --git a/foundryup-zksync/foundryup-zksync b/foundryup-zksync/foundryup-zksync index b24afa6f2..245fc880f 100755 --- a/foundryup-zksync/foundryup-zksync +++ b/foundryup-zksync/foundryup-zksync @@ -235,6 +235,8 @@ Update or revert to a specific Foundry-zksync version with ease. By default, the latest nightly version is installed from built binaries. +By default, the latest nightly version is installed from built binaries. + USAGE: foundryup-zksync @@ -318,7 +320,7 @@ banner() { Fork of : https://github.com/foundry-rs/ Repo : https://github.com/foundry-zksync/ -Book : https://book.getfoundry.sh/ +Book : https://book.getfoundry.sh/ .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index edf40f227..c76457b23 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -318,6 +318,10 @@ interface Vm { function deal(address account, uint256 newBalance) external; function deleteSnapshot(uint256 snapshotId) external returns (bool success); function deleteSnapshots() external; + function deployCode(string calldata artifactPath) external returns (address deployedAddress); + function deployCode(string calldata artifactPath, bytes calldata constructorArgs) + external + returns (address deployedAddress); function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external @@ -398,6 +402,18 @@ interface Vm { function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external; function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external; function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external; + function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) + external; + function expectEmitAnonymous( + bool checkTopic0, + bool checkTopic1, + bool checkTopic2, + bool checkTopic3, + bool checkData, + address emitter + ) external; + function expectEmitAnonymous() external; + function expectEmitAnonymous(address emitter) external; function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external; @@ -471,6 +487,18 @@ interface Vm { function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys); function parseJsonString(string calldata json, string calldata key) external pure returns (string memory); function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory); + function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) + external + pure + returns (bytes memory); + function parseJsonType(string calldata json, string calldata typeDescription) + external + pure + returns (bytes memory); + function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) + external + pure + returns (bytes memory); function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256); function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); @@ -544,10 +572,13 @@ interface Vm { function rollFork(bytes32 txHash) external; function rollFork(uint256 forkId, uint256 blockNumber) external; function rollFork(uint256 forkId, bytes32 txHash) external; - function rpc(string calldata method, string calldata params) external returns (bytes memory data); function rpcUrl(string calldata rpcAlias) external view returns (string memory json); function rpcUrlStructs() external view returns (Rpc[] memory urls); function rpcUrls() external view returns (string[2][] memory urls); + function rpc(string calldata method, string calldata params) external returns (bytes memory data); + function rpc(string calldata urlOrAlias, string calldata method, string calldata params) + external + returns (bytes memory data); function selectFork(uint256 forkId) external; function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external @@ -580,6 +611,16 @@ interface Vm { external returns (string memory json); function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); + function serializeJsonType(string calldata typeDescription, bytes memory value) + external + pure + returns (string memory json); + function serializeJsonType( + string calldata objectKey, + string calldata valueKey, + string calldata typeDescription, + bytes memory value + ) external returns (string memory json); function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); @@ -595,6 +636,7 @@ interface Vm { function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json); + function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; function setEnv(string calldata name, string calldata value) external; function setNonce(address account, uint64 newNonce) external; function setNonceUnsafe(address account, uint64 newNonce) external; diff --git a/testdata/default/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol index b33af6292..971bb5e27 100644 --- a/testdata/default/cheats/Assert.t.sol +++ b/testdata/default/cheats/Assert.t.sol @@ -53,16 +53,12 @@ contract AssertionsTest is DSTest { } function _formatWithDecimals(int256 value, uint256 decimals) internal returns (string memory) { - string memory intPart = vm.toString(value / int256(10 ** decimals)); - int256 mod = value % int256(10 ** decimals); - string memory decimalPart = vm.toString(mod > 0 ? mod : -mod); - - // Add - if we have something like 0.123 - if ((value < 0) && keccak256(abi.encode(intPart)) == keccak256(abi.encode("0"))) { - intPart = string.concat("-", intPart); + string memory formatted = _formatWithDecimals(_abs(value), decimals); + if (value < 0) { + formatted = string.concat("-", formatted); } - return _prefixDecWithZeroes(intPart, decimalPart, decimals); + return formatted; } function testFuzzAssertEqNotEq(uint256 left, uint256 right, uint256 decimals) public { diff --git a/testdata/default/cheats/DeployCode.t.sol b/testdata/default/cheats/DeployCode.t.sol new file mode 100644 index 000000000..330e82651 --- /dev/null +++ b/testdata/default/cheats/DeployCode.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} + +contract TestContractWithArgs { + uint256 public a; + uint256 public b; + + constructor(uint256 _a, uint256 _b) { + a = _a; + b = _b; + } +} + +contract DeployCodeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + address public constant overrideAddress = 0x0000000000000000000000000000000000000064; + + event Payload(address sender, address target, bytes data); + + function testDeployCode() public { + address addrDefault = address(new TestContract()); + address addrDeployCode = vm.deployCode("cheats/DeployCode.t.sol:TestContract"); + + assertEq(addrDefault.code, addrDeployCode.code); + } + + function testDeployCodeWithArgs() public { + address withNew = address(new TestContractWithArgs(1, 2)); + TestContractWithArgs withDeployCode = + TestContractWithArgs(vm.deployCode("cheats/DeployCode.t.sol:TestContractWithArgs", abi.encode(3, 4))); + + assertEq(withNew.code, address(withDeployCode).code); + assertEq(withDeployCode.a(), 3); + assertEq(withDeployCode.b(), 4); + } +} diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 4b4053334..da382e90e 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -228,6 +228,12 @@ contract ForkTest is DSTest { bytes memory result = vm.rpc("eth_getBalance", file); assertEq(hex"10b7c11bcb51e6", result); } + + function testRpcWithUrl() public { + bytes memory result = vm.rpc("rpcAlias", "eth_blockNumber", "[]"); + uint256 decodedResult = vm.parseUint(vm.toString(result)); + assertGt(decodedResult, 20_000_000); + } } contract DummyContract { diff --git a/testdata/default/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol index ca53b1801..0604ef907 100644 --- a/testdata/default/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -5,7 +5,89 @@ import "ds-test/test.sol"; import "cheats/Vm.sol"; import "../logs/console.sol"; +library JsonStructs { + address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); + Vm constant vm = Vm(HEVM_ADDRESS); + + // forge eip712 testdata/default/cheats/Json.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatJson + string constant schema_FlatJson = + "FlatJson(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Json.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedJson + string constant schema_NestedJson = + "NestedJson(FlatJson[] members,AnotherFlatJson inner,string name)AnotherFlatJson(bytes4 fixedBytes)FlatJson(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function deserializeFlatJson(string memory json) internal pure returns (ParseJsonTest.FlatJson memory) { + return abi.decode(vm.parseJsonType(json, schema_FlatJson), (ParseJsonTest.FlatJson)); + } + + function deserializeFlatJson(string memory json, string memory path) + internal + pure + returns (ParseJsonTest.FlatJson memory) + { + return abi.decode(vm.parseJsonType(json, path, schema_FlatJson), (ParseJsonTest.FlatJson)); + } + + function deserializeFlatJsonArray(string memory json, string memory path) + internal + pure + returns (ParseJsonTest.FlatJson[] memory) + { + return abi.decode(vm.parseJsonTypeArray(json, path, schema_FlatJson), (ParseJsonTest.FlatJson[])); + } + + function deserializeNestedJson(string memory json) internal pure returns (ParseJsonTest.NestedJson memory) { + return abi.decode(vm.parseJsonType(json, schema_NestedJson), (ParseJsonTest.NestedJson)); + } + + function deserializeNestedJson(string memory json, string memory path) + internal + pure + returns (ParseJsonTest.NestedJson memory) + { + return abi.decode(vm.parseJsonType(json, path, schema_NestedJson), (ParseJsonTest.NestedJson)); + } + + function deserializeNestedJsonArray(string memory json, string memory path) + internal + pure + returns (ParseJsonTest.NestedJson[] memory) + { + return abi.decode(vm.parseJsonType(json, path, schema_NestedJson), (ParseJsonTest.NestedJson[])); + } + + function serialize(ParseJsonTest.FlatJson memory instance) internal pure returns (string memory) { + return vm.serializeJsonType(schema_FlatJson, abi.encode(instance)); + } + + function serialize(ParseJsonTest.NestedJson memory instance) internal pure returns (string memory) { + return vm.serializeJsonType(schema_NestedJson, abi.encode(instance)); + } +} + contract ParseJsonTest is DSTest { + using JsonStructs for *; + + struct FlatJson { + uint256 a; + int24[][] arr; + string str; + bytes b; + address addr; + bytes32 fixedBytes; + } + + struct AnotherFlatJson { + bytes4 fixedBytes; + } + + struct NestedJson { + FlatJson[] members; + AnotherFlatJson inner; + string name; + } + Vm constant vm = Vm(HEVM_ADDRESS); string json; @@ -97,7 +179,7 @@ contract ParseJsonTest is DSTest { } function test_coercionRevert() public { - vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); + vm._expectCheatcodeRevert("expected uint256, found JSON object"); vm.parseJsonUint(json, ".nestedObject"); } @@ -206,6 +288,44 @@ contract ParseJsonTest is DSTest { vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); vm.parseJsonKeys(jsonString, ".*"); } + + // forge eip712 testdata/default/cheats/Json.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatJson + string constant schema_FlatJson = + "FlatJson(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Json.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedJson + string constant schema_NestedJson = + "NestedJson(FlatJson[] members,AnotherFlatJson inner,string name)AnotherFlatJson(bytes4 fixedBytes)FlatJson(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function test_parseJsonType() public { + string memory readJson = vm.readFile("fixtures/Json/nested_json_struct.json"); + NestedJson memory data = readJson.deserializeNestedJson(); + assertEq(data.members.length, 2); + + FlatJson memory expected = FlatJson({ + a: 200, + arr: new int24[][](0), + str: "some other string", + b: hex"0000000000000000000000000000000000000000", + addr: 0x167D91deaEEE3021161502873d3bcc6291081648, + fixedBytes: 0xed1c7beb1f00feaaaec5636950d6edb25a8d4fedc8deb2711287b64c4d27719d + }); + + assertEq(keccak256(abi.encode(data.members[1])), keccak256(abi.encode(expected))); + assertEq(bytes32(data.inner.fixedBytes), bytes32(bytes4(0x12345678))); + + FlatJson[] memory members = JsonStructs.deserializeFlatJsonArray(readJson, ".members"); + + assertEq(keccak256(abi.encode(members)), keccak256(abi.encode(data.members))); + } + + function test_parseJsonType_roundtrip() public { + string memory readJson = vm.readFile("fixtures/Json/nested_json_struct.json"); + NestedJson memory data = readJson.deserializeNestedJson(); + string memory serialized = data.serialize(); + NestedJson memory deserialized = serialized.deserializeNestedJson(); + assertEq(keccak256(abi.encode(data)), keccak256(abi.encode(deserialized))); + } } contract WriteJsonTest is DSTest { @@ -277,13 +397,13 @@ contract WriteJsonTest is DSTest { // Github issue: https://github.com/foundry-rs/foundry/issues/5745 function test_serializeRootObject() public { string memory serialized = vm.serializeJson(json1, '{"foo": "bar"}'); - assertEq(serialized, '{"foo":"bar"}'); + assertEq(serialized, '{"foo": "bar"}'); serialized = vm.serializeBool(json1, "boolean", true); assertEq(vm.parseJsonString(serialized, ".foo"), "bar"); assertEq(vm.parseJsonBool(serialized, ".boolean"), true); string memory overwritten = vm.serializeJson(json1, '{"value": 123}'); - assertEq(overwritten, '{"value":123}'); + assertEq(overwritten, '{"value": 123}'); } struct simpleJson { diff --git a/testdata/default/cheats/RandomUint.t.sol b/testdata/default/cheats/RandomUint.t.sol index 5c5b1024a..e679f9bfd 100644 --- a/testdata/default/cheats/RandomUint.t.sol +++ b/testdata/default/cheats/RandomUint.t.sol @@ -8,8 +8,26 @@ contract RandomUint is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testRandomUint() public { - uint256 rand = vm.randomUint(); + vm.randomUint(); + } + + function testRandomUintRangeOverflow() public { + vm.randomUint(0, uint256(int256(-1))); + } + + function testRandomUintSame(uint256 val) public { + uint256 rand = vm.randomUint(val, val); + assertTrue(rand == val); + } + + function testRandomUintRange(uint256 min, uint256 max) public { + vm.assume(max >= min); + uint256 rand = vm.randomUint(min, max); + assertTrue(rand >= min, "rand >= min"); + assertTrue(rand <= max, "rand <= max"); + } - assertTrue(rand > 0); + function testRandomAddress() public { + vm.randomAddress(); } } diff --git a/testdata/default/cheats/SetBlockhash.t.sol b/testdata/default/cheats/SetBlockhash.t.sol new file mode 100644 index 000000000..f6c2af5f6 --- /dev/null +++ b/testdata/default/cheats/SetBlockhash.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SetBlockhash is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSetBlockhash() public { + bytes32 blockHash = 0x1234567890123456789012345678901234567890123456789012345678901234; + vm.setBlockhash(block.number - 1, blockHash); + bytes32 expected = blockhash(block.number - 1); + assertEq(blockHash, expected); + } +} diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol index 40667743f..a01b29af6 100644 --- a/testdata/default/cheats/Toml.t.sol +++ b/testdata/default/cheats/Toml.t.sol @@ -116,7 +116,7 @@ contract ParseTomlTest is DSTest { } function test_coercionRevert() public { - vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); + vm._expectCheatcodeRevert("expected uint256, found JSON object"); vm.parseTomlUint(toml, ".nestedObject"); } diff --git a/testdata/default/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol index 786c1ef59..a6b683967 100644 --- a/testdata/default/cheats/UnixTime.t.sol +++ b/testdata/default/cheats/UnixTime.t.sol @@ -8,21 +8,21 @@ contract UnixTimeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); // This is really wide because CI sucks. - uint256 constant errMargin = 500; + uint256 constant errMargin = 1000; function testUnixTimeAgainstDate() public { string[] memory inputs = new string[](2); inputs[0] = "date"; - // OS X does not support precision more than 1 second + // OS X does not support precision more than 1 second. inputs[1] = "+%s000"; bytes memory res = vm.ffi(inputs); uint256 date = vm.parseUint(string(res)); - // Limit precision to 1000 ms + // Limit precision to 1000 ms. uint256 time = vm.unixTime() / 1000 * 1000; - assertEq(date, time, ".unixTime() is inaccurate"); + vm.assertApproxEqAbs(date, time, errMargin, ".unixTime() is inaccurate vs date"); } function testUnixTime() public { diff --git a/testdata/default/core/LegacyAssertions.t.sol b/testdata/default/core/LegacyAssertions.t.sol new file mode 100644 index 000000000..9bbc56e8e --- /dev/null +++ b/testdata/default/core/LegacyAssertions.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract NoAssertionsRevertTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testMultipleAssertFailures() public { + vm.assertEq(uint256(1), uint256(2)); + vm.assertLt(uint256(5), uint256(4)); + } +} + +contract LegacyAssertionsTest { + bool public failed; + + function testFlagNotSetSuccess() public {} + + function testFlagSetFailure() public { + failed = true; + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol index 06b4b21d7..f439b8ce1 100644 --- a/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol @@ -7,7 +7,7 @@ contract Malicious { function world() public { // add code so contract is accounted as valid sender // see https://github.com/foundry-rs/foundry/issues/4245 - payable(msg.sender).transfer(1); + payable(msg.sender).call(""); } } diff --git a/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol b/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol index 918fd7b01..aea46f418 100644 --- a/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import "ds-test/test.sol"; -contract HandlerWithOneSelector { +contract HandlerOne { uint256 public hit1; function selector1() external { @@ -11,12 +11,11 @@ contract HandlerWithOneSelector { } } -contract HandlerWithFiveSelectors { +contract HandlerTwo { uint256 public hit2; uint256 public hit3; uint256 public hit4; uint256 public hit5; - uint256 public hit6; function selector2() external { hit2 += 1; @@ -33,69 +32,25 @@ contract HandlerWithFiveSelectors { function selector5() external { hit5 += 1; } - - function selector6() external { - hit6 += 1; - } -} - -contract HandlerWithFourSelectors { - uint256 public hit7; - uint256 public hit8; - uint256 public hit9; - uint256 public hit10; - - function selector7() external { - hit7 += 1; - } - - function selector8() external { - hit8 += 1; - } - - function selector9() external { - hit9 += 1; - } - - function selector10() external { - hit10 += 1; - } } contract InvariantSelectorsWeightTest is DSTest { - HandlerWithOneSelector handlerOne; - HandlerWithFiveSelectors handlerTwo; - HandlerWithFourSelectors handlerThree; + HandlerOne handlerOne; + HandlerTwo handlerTwo; function setUp() public { - handlerOne = new HandlerWithOneSelector(); - handlerTwo = new HandlerWithFiveSelectors(); - handlerThree = new HandlerWithFourSelectors(); + handlerOne = new HandlerOne(); + handlerTwo = new HandlerTwo(); } function afterInvariant() public { - // selector hits before and after https://github.com/foundry-rs/foundry/issues/2986 - // hit1: 11 | hit2: 4 | hit3: 0 | hit4: 0 | hit5: 4 | hit6: 1 | hit7: 2 | hit8: 2 | hit9: 2 | hit10: 4 - // hit1: 2 | hit2: 5 | hit3: 4 | hit4: 5 | hit5: 3 | hit6: 1 | hit7: 4 | hit8: 1 | hit9: 1 | hit10: 4 - - uint256 hit1 = handlerOne.hit1(); - uint256 hit2 = handlerTwo.hit2(); - uint256 hit3 = handlerTwo.hit3(); - uint256 hit4 = handlerTwo.hit4(); - uint256 hit5 = handlerTwo.hit5(); - uint256 hit6 = handlerTwo.hit6(); - uint256 hit7 = handlerThree.hit7(); - uint256 hit8 = handlerThree.hit8(); - uint256 hit9 = handlerThree.hit9(); - uint256 hit10 = handlerThree.hit10(); - - require( - hit1 > 0 && hit2 > 0 && hit3 > 0 && hit4 > 0 && hit5 > 0 && hit6 > 0 && hit7 > 0 && hit8 > 0 && hit9 > 0 - && hit10 > 0 - ); + // 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); } - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 30 function invariant_selectors_weight() public view {} } diff --git a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol b/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol new file mode 100644 index 000000000..993d806f8 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +contract SequenceNoReverts { + uint256 public count; + + function work(uint256 x) public { + require(x % 2 != 0); + count++; + } +} + +contract SequenceNoRevertsTest is DSTest { + SequenceNoReverts target; + + function setUp() public { + target = new SequenceNoReverts(); + } + + function invariant_no_reverts() public view { + require(target.count() < 10, "condition met"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol index d5dcfe674..34d11ccb3 100644 --- a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol @@ -88,26 +88,6 @@ contract InvariantShrinkWithAssert is DSTest { function invariant_with_assert() public { assertTrue(counter.number() != 3, "wrong counter"); } -} - -contract InvariantShrinkWithRequire is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - Counter public counter; - Handler handler; - - function setUp() public { - counter = new Counter(); - handler = new Handler(counter); - } - - function targetSelectors() public returns (FuzzSelector[] memory) { - FuzzSelector[] memory targets = new FuzzSelector[](1); - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = handler.increment.selector; - selectors[1] = handler.setNumber.selector; - targets[0] = FuzzSelector(address(handler), selectors); - return targets; - } function invariant_with_require() public { require(counter.number() != 3, "wrong counter"); diff --git a/testdata/default/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol index f6cf60fe7..378931312 100644 --- a/testdata/default/inline/FuzzInlineConf.t.sol +++ b/testdata/default/inline/FuzzInlineConf.t.sol @@ -12,3 +12,15 @@ contract FuzzInlineConf is DSTest { require(true, "this is not going to revert"); } } + +/// forge-config: default.fuzz.runs = 10 +contract FuzzInlineConf2 is DSTest { + /// forge-config: default.fuzz.runs = 1 + function testInlineConfFuzz1(uint8 x) public { + require(true, "this is not going to revert"); + } + + function testInlineConfFuzz2(uint8 x) public { + require(true, "this is not going to revert"); + } +} diff --git a/testdata/default/repros/Issue7457.t.sol b/testdata/default/repros/Issue7457.t.sol new file mode 100644 index 000000000..8d9d6f075 --- /dev/null +++ b/testdata/default/repros/Issue7457.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface ITarget { + event AnonymousEventEmpty() anonymous; + event AnonymousEventNonIndexed(uint256 a) anonymous; + + event DifferentAnonymousEventEmpty() anonymous; + event DifferentAnonymousEventNonIndexed(string a) anonymous; + + event AnonymousEventWith1Topic(uint256 indexed a, uint256 b) anonymous; + event AnonymousEventWith2Topics(uint256 indexed a, uint256 indexed b, uint256 c) anonymous; + event AnonymousEventWith3Topics(uint256 indexed a, uint256 indexed b, uint256 indexed c, uint256 d) anonymous; + event AnonymousEventWith4Topics( + uint256 indexed a, uint256 indexed b, uint256 indexed c, uint256 indexed d, uint256 e + ) anonymous; +} + +contract Target is ITarget { + function emitAnonymousEventEmpty() external { + emit AnonymousEventEmpty(); + } + + function emitAnonymousEventNonIndexed(uint256 a) external { + emit AnonymousEventNonIndexed(a); + } + + function emitAnonymousEventWith1Topic(uint256 a, uint256 b) external { + emit AnonymousEventWith1Topic(a, b); + } + + function emitAnonymousEventWith2Topics(uint256 a, uint256 b, uint256 c) external { + emit AnonymousEventWith2Topics(a, b, c); + } + + function emitAnonymousEventWith3Topics(uint256 a, uint256 b, uint256 c, uint256 d) external { + emit AnonymousEventWith3Topics(a, b, c, d); + } + + function emitAnonymousEventWith4Topics(uint256 a, uint256 b, uint256 c, uint256 d, uint256 e) external { + emit AnonymousEventWith4Topics(a, b, c, d, e); + } +} + +// https://github.com/foundry-rs/foundry/issues/7457 +contract Issue7457Test is DSTest, ITarget { + Vm constant vm = Vm(HEVM_ADDRESS); + + Target public target; + + function setUp() external { + target = new Target(); + } + + function testEmitEvent() public { + vm.expectEmitAnonymous(false, false, false, false, true); + emit AnonymousEventEmpty(); + target.emitAnonymousEventEmpty(); + } + + function testFailEmitEventNonIndexed() public { + vm.expectEmit(false, false, false, true); + emit AnonymousEventNonIndexed(1); + target.emitAnonymousEventNonIndexed(1); + } + + function testEmitEventNonIndexed() public { + vm.expectEmitAnonymous(false, false, false, false, true); + emit AnonymousEventNonIndexed(1); + target.emitAnonymousEventNonIndexed(1); + } + + // function testFailEmitDifferentEvent() public { + // vm.expectEmitAnonymous(false, false, false, true); + // emit DifferentAnonymousEventEmpty(); + // target.emitAnonymousEventEmpty(); + // } + + function testFailEmitDifferentEventNonIndexed() public { + vm.expectEmitAnonymous(false, false, false, false, true); + emit DifferentAnonymousEventNonIndexed("1"); + target.emitAnonymousEventNonIndexed(1); + } + + function testEmitEventWith1Topic() public { + vm.expectEmitAnonymous(true, false, false, false, true); + emit AnonymousEventWith1Topic(1, 2); + target.emitAnonymousEventWith1Topic(1, 2); + } + + function testEmitEventWith2Topics() public { + vm.expectEmitAnonymous(true, true, false, false, true); + emit AnonymousEventWith2Topics(1, 2, 3); + target.emitAnonymousEventWith2Topics(1, 2, 3); + } + + function testEmitEventWith3Topics() public { + vm.expectEmitAnonymous(true, true, true, false, true); + emit AnonymousEventWith3Topics(1, 2, 3, 4); + target.emitAnonymousEventWith3Topics(1, 2, 3, 4); + } + + function testEmitEventWith4Topics() public { + vm.expectEmitAnonymous(true, true, true, true, true); + emit AnonymousEventWith4Topics(1, 2, 3, 4, 5); + target.emitAnonymousEventWith4Topics(1, 2, 3, 4, 5); + } +} diff --git a/testdata/default/repros/Issue8168.t.sol b/testdata/default/repros/Issue8168.t.sol new file mode 100644 index 000000000..b9bd5757a --- /dev/null +++ b/testdata/default/repros/Issue8168.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/8168 +contract Issue8168Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testForkWarpRollPreserved() public { + uint256 fork1 = vm.createFork("rpcAlias"); + uint256 fork2 = vm.createFork("rpcAlias"); + + vm.selectFork(fork1); + uint256 initial_fork1_number = block.number; + uint256 initial_fork1_ts = block.timestamp; + vm.warp(block.timestamp + 1000); + vm.roll(block.number + 100); + assertEq(block.timestamp, initial_fork1_ts + 1000); + assertEq(block.number, initial_fork1_number + 100); + + vm.selectFork(fork2); + uint256 initial_fork2_number = block.number; + uint256 initial_fork2_ts = block.timestamp; + vm.warp(block.timestamp + 2000); + vm.roll(block.number + 200); + assertEq(block.timestamp, initial_fork2_ts + 2000); + assertEq(block.number, initial_fork2_number + 200); + + vm.selectFork(fork1); + assertEq(block.timestamp, initial_fork1_ts + 1000); + assertEq(block.number, initial_fork1_number + 100); + + vm.selectFork(fork2); + assertEq(block.timestamp, initial_fork2_ts + 2000); + assertEq(block.number, initial_fork2_number + 200); + } +} diff --git a/testdata/default/repros/Issue8277.t.sol b/testdata/default/repros/Issue8277.t.sol new file mode 100644 index 000000000..aebdbd8ff --- /dev/null +++ b/testdata/default/repros/Issue8277.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/8277 +contract Issue8277Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + struct MyJson { + string s; + } + + function test_hexprefixednonhexstring() public { + { + bytes memory b = vm.parseJson("{\"a\": \"0x834629f473876e5f0d3d9d269af3dabcb0d7d520-identifier-0\"}"); + MyJson memory decoded = abi.decode(b, (MyJson)); + assertEq(decoded.s, "0x834629f473876e5f0d3d9d269af3dabcb0d7d520-identifier-0"); + } + + { + bytes memory b = vm.parseJson("{\"b\": \"0xBTC\"}"); + MyJson memory decoded = abi.decode(b, (MyJson)); + assertEq(decoded.s, "0xBTC"); + } + } +} diff --git a/testdata/default/repros/Issue8287.t.sol b/testdata/default/repros/Issue8287.t.sol new file mode 100644 index 000000000..cedcb4043 --- /dev/null +++ b/testdata/default/repros/Issue8287.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/8287 +contract Issue8287Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRpcBalance() public { + uint256 f2 = vm.createSelectFork("rpcAlias", 10); + bytes memory data = vm.rpc("eth_getBalance", "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"); + string memory m = vm.toString(data); + assertEq(m, "0x2086ac351052600000"); + } + + function testRpcStorage() public { + uint256 f2 = vm.createSelectFork("rpcAlias", 10); + bytes memory data = vm.rpc( + "eth_getStorageAt", + "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x40BdB4497614bAe1A67061EE20AAdE3c2067AC9e\",\"0x0\"]" + ); + string memory m = vm.toString(data); + assertEq(m, "0x0000000000000000000000000000000000000000000000000000000000000000"); + } +} diff --git a/testdata/default/repros/Issue8383.t.sol b/testdata/default/repros/Issue8383.t.sol new file mode 100644 index 000000000..a002b4b3d --- /dev/null +++ b/testdata/default/repros/Issue8383.t.sol @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/8383 +contract Issue8383Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + address internal _verifier; + + mapping(bytes32 => bool) internal _vectorTested; + mapping(bytes32 => bool) internal _vectorResult; + + function setUp() public { + _verifier = address(new P256Verifier()); + } + + function _verifyViaVerifier(bytes32 hash, uint256 r, uint256 s, uint256 x, uint256 y) internal returns (bool) { + return _verifyViaVerifier(hash, bytes32(r), bytes32(s), bytes32(x), bytes32(y)); + } + + function _verifyViaVerifier(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) internal returns (bool) { + bytes memory payload = abi.encode(hash, r, s, x, y); + if (uint256(y) & 0xff == 0) { + bytes memory truncatedPayload = abi.encodePacked(hash, r, s, x, bytes31(y)); + _verifierCall(truncatedPayload); + } + if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) { + uint256 r = uint256(keccak256(abi.encode(payload, "2"))); + payload = abi.encodePacked(payload, new bytes(r & 0xff)); + } + bytes32 payloadHash = keccak256(payload); + if (_vectorTested[payloadHash]) return _vectorResult[payloadHash]; + _vectorTested[payloadHash] = true; + return (_vectorResult[payloadHash] = _verifierCall(payload)); + } + + function _verifierCall(bytes memory payload) internal returns (bool) { + (bool success, bytes memory result) = _verifier.call(payload); + return abi.decode(result, (bool)); + } + + function testP256VerifyOutOfBounds() public { + vm.pauseGasMetering(); + uint256 p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + _verifyViaVerifier(bytes32(0), 1, 1, 1, 1); + _verifyViaVerifier(bytes32(0), 1, 1, 0, 1); + _verifyViaVerifier(bytes32(0), 1, 1, 1, 0); + _verifyViaVerifier(bytes32(0), 1, 1, 1, p); + _verifyViaVerifier(bytes32(0), 1, 1, p, 1); + _verifyViaVerifier(bytes32(0), 1, 1, p - 1, 1); + vm.resumeGasMetering(); + } +} + +contract P256Verifier { + uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + uint256 private constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; // `A = P - 3`. + uint256 private constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + uint256 private constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + + fallback() external payable { + assembly { + // For this implementation, we will use the memory without caring about + // the free memory pointer or zero pointer. + // The slots `0x00`, `0x20`, `0x40`, `0x60`, will not be accessed for the `Points[16]` array, + // and can be used for storing other variables. + + mstore(0x40, P) // Set `0x40` to `P`. + + function jAdd(x1, y1, z1, x2, y2, z2) -> x3, y3, z3 { + if iszero(z1) { + x3 := x2 + y3 := y2 + z3 := z2 + leave + } + if iszero(z2) { + x3 := x1 + y3 := y1 + z3 := z1 + leave + } + let p := mload(0x40) + let zz1 := mulmod(z1, z1, p) + let zz2 := mulmod(z2, z2, p) + let u1 := mulmod(x1, zz2, p) + let u2 := mulmod(x2, zz1, p) + let s1 := mulmod(y1, mulmod(zz2, z2, p), p) + let s2 := mulmod(y2, mulmod(zz1, z1, p), p) + let h := addmod(u2, sub(p, u1), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r := addmod(s2, sub(p, s1), p) + x3 := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) + y3 := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, x3), p), p), sub(p, mulmod(s1, hhh, p)), p) + z3 := mulmod(h, mulmod(z1, z2, p), p) + } + + function setJPoint(i, x, y, z) { + // We will multiply by `0x80` (i.e. `shl(7, i)`) instead + // since the memory expansion costs are cheaper than doing `mul(0x60, i)`. + // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`. + i := shl(7, i) + mstore(i, x) + mstore(add(i, returndatasize()), y) + mstore(add(i, 0x40), z) + } + + function setJPointDouble(i, j) { + j := shl(7, j) + let x := mload(j) + let y := mload(add(j, returndatasize())) + let z := mload(add(j, 0x40)) + let p := mload(0x40) + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + setJPoint(i, x2, y2, z2) + } + + function setJPointAdd(i, j, k) { + j := shl(7, j) + k := shl(7, k) + let x, y, z := + jAdd( + mload(j), + mload(add(j, returndatasize())), + mload(add(j, 0x40)), + mload(k), + mload(add(k, returndatasize())), + mload(add(k, 0x40)) + ) + setJPoint(i, x, y, z) + } + + let r := calldataload(0x20) + let n := N + + { + let s := calldataload(0x40) + if lt(shr(1, n), s) { s := sub(n, s) } + + // Perform `modExp(s, N - 2, N)`. + // After which, we can abuse `returndatasize()` to get `0x20`. + mstore(0x800, 0x20) + mstore(0x820, 0x20) + mstore(0x840, 0x20) + mstore(0x860, s) + mstore(0x880, sub(n, 2)) + mstore(0x8a0, n) + + let p := mload(0x40) + mstore(0x20, xor(3, p)) // Set `0x20` to `A`. + let Qx := calldataload(0x60) + let Qy := calldataload(0x80) + + if iszero( + and( // The arguments of `and` are evaluated last to first. + and( + and(gt(calldatasize(), 0x9f), and(lt(iszero(r), lt(r, n)), lt(iszero(s), lt(s, n)))), + eq( + mulmod(Qy, Qy, p), + addmod(mulmod(addmod(mulmod(Qx, Qx, p), mload(returndatasize()), p), Qx, p), B, p) + ) + ), + and( + // We need to check that the `returndatasize` is indeed 32, + // so that we can return false if the chain does not have the modexp precompile. + eq(returndatasize(), 0x20), + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), 0x20) + ) + ) + ) { + // POC Note: + // Changing this to `return(0x80, 0x20)` fixes it. + // Alternatively, adding `if mload(0x8c0) { invalid() }` just before the return also fixes it. + return(0x8c0, 0x20) + } + + setJPoint(0x01, Qx, Qy, 1) + setJPoint(0x04, GX, GY, 1) + setJPointDouble(0x02, 0x01) + setJPointDouble(0x08, 0x04) + setJPointAdd(0x03, 0x01, 0x02) + setJPointAdd(0x05, 0x01, 0x04) + setJPointAdd(0x06, 0x02, 0x04) + setJPointAdd(0x07, 0x03, 0x04) + setJPointAdd(0x09, 0x01, 0x08) + setJPointAdd(0x0a, 0x02, 0x08) + setJPointAdd(0x0b, 0x03, 0x08) + setJPointAdd(0x0c, 0x04, 0x08) + setJPointAdd(0x0d, 0x01, 0x0c) + setJPointAdd(0x0e, 0x02, 0x0c) + setJPointAdd(0x0f, 0x03, 0x0c) + } + + let i := 0 + let u1 := mulmod(calldataload(0x00), mload(0x00), n) + let u2 := mulmod(r, mload(0x00), n) + let y := 0 + let z := 0 + let x := 0 + let p := mload(0x40) + for {} 1 {} { + if z { + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + yy := mulmod(y2, y2, p) + zz := mulmod(z2, z2, p) + s := mulmod(4, mulmod(x2, yy, p), p) + m := + addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + z := mulmod(2, mulmod(y2, z2, p), p) + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + } + for { let o := or(and(shr(245, shl(i, u1)), 0x600), and(shr(247, shl(i, u2)), 0x180)) } o {} { + let z2 := mload(add(o, 0x40)) + if iszero(z2) { break } + if iszero(z) { + x := mload(o) + y := mload(add(o, returndatasize())) + z := z2 + break + } + let zz1 := mulmod(z, z, p) + let zz2 := mulmod(z2, z2, p) + let u1_ := mulmod(x, zz2, p) + let s1 := mulmod(y, mulmod(zz2, z2, p), p) + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) + z := mulmod(h, mulmod(z, z2, p), p) + break + } + // Just unroll twice. Fully unrolling will only save around 1% to 2% gas, but make the + // bytecode very bloated, which may incur more runtime costs after Verkle. + // See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip + // It's very unlikely that Verkle will come before the P256 precompile. But who knows? + if z { + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) + let m := + addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + let z2 := mulmod(2, mulmod(y, z, p), p) + yy := mulmod(y2, y2, p) + zz := mulmod(z2, z2, p) + s := mulmod(4, mulmod(x2, yy, p), p) + m := + addmod(mulmod(3, mulmod(x2, x2, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p) + x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + z := mulmod(2, mulmod(y2, z2, p), p) + y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + } + for { let o := or(and(shr(243, shl(i, u1)), 0x600), and(shr(245, shl(i, u2)), 0x180)) } o {} { + let z2 := mload(add(o, 0x40)) + if iszero(z2) { break } + if iszero(z) { + x := mload(o) + y := mload(add(o, returndatasize())) + z := z2 + break + } + let zz1 := mulmod(z, z, p) + let zz2 := mulmod(z2, z2, p) + let u1_ := mulmod(x, zz2, p) + let s1 := mulmod(y, mulmod(zz2, z2, p), p) + let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p) + let hh := mulmod(h, h, p) + let hhh := mulmod(h, hh, p) + let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p) + x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p) + y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p) + z := mulmod(h, mulmod(z, z2, p), p) + break + } + i := add(i, 4) + if eq(i, 256) { break } + } + + if iszero(z) { + mstore(returndatasize(), iszero(r)) + return(returndatasize(), 0x20) + } + + // Perform `modExp(z, P - 2, P)`. + // `0x800`, `0x820, `0x840` are still set to `0x20`. + mstore(0x860, z) + mstore(0x880, sub(p, 2)) + mstore(0x8a0, p) + + mstore( + returndatasize(), + and( // The arguments of `and` are evaluated last to first. + eq(mod(mulmod(x, mulmod(mload(returndatasize()), mload(returndatasize()), p), p), n), r), + staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), returndatasize()) + ) + ) + return(returndatasize(), returndatasize()) + } + } +} diff --git a/testdata/default/vyper/Counter.vy b/testdata/default/vyper/Counter.vy new file mode 100644 index 000000000..772bddd11 --- /dev/null +++ b/testdata/default/vyper/Counter.vy @@ -0,0 +1,12 @@ +from . import ICounter +implements: ICounter + +number: public(uint256) + +@external +def set_number(new_number: uint256): + self.number = new_number + +@external +def increment(): + self.number += 1 diff --git a/testdata/default/vyper/CounterTest.vy b/testdata/default/vyper/CounterTest.vy new file mode 100644 index 000000000..b6cc517d2 --- /dev/null +++ b/testdata/default/vyper/CounterTest.vy @@ -0,0 +1,16 @@ +from . import ICounter + +interface Vm: + def deployCode(artifact_name: String[1024], args: Bytes[1024] = b"") -> address: nonpayable + +vm: constant(Vm) = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) +counter: ICounter + +@external +def setUp(): + self.counter = ICounter(extcall vm.deployCode("vyper/Counter.vy")) + +@external +def test_increment(): + extcall self.counter.increment() + assert staticcall self.counter.number() == 1 diff --git a/testdata/default/vyper/ICounter.vyi b/testdata/default/vyper/ICounter.vyi new file mode 100644 index 000000000..e600c71c8 --- /dev/null +++ b/testdata/default/vyper/ICounter.vyi @@ -0,0 +1,12 @@ +@view +@external +def number() -> uint256: + ... + +@external +def set_number(new_number: uint256): + ... + +@external +def increment(): + ... \ No newline at end of file diff --git a/testdata/fixtures/Json/nested_json_struct.json b/testdata/fixtures/Json/nested_json_struct.json new file mode 100644 index 000000000..ac6fe7692 --- /dev/null +++ b/testdata/fixtures/Json/nested_json_struct.json @@ -0,0 +1,35 @@ +{ + "members": [ + { + "a": 100, + "arr": [ + [ + 1, + -2, + -5 + ], + [ + 1000, + 2000, + 0 + ] + ], + "str": "some string", + "b": "0x", + "addr": "0x0000000000000000000000000000000000000000", + "fixedBytes": "0x8ae3fc6bd1b150a73ec4afe3ef136fa2f88e9c96131c883c5e4a4714811c1598" + }, + { + "a": 200, + "arr": [], + "str": "some other string", + "b": "0x0000000000000000000000000000000000000000", + "addr": "0x167D91deaEEE3021161502873d3bcc6291081648", + "fixedBytes": "0xed1c7beb1f00feaaaec5636950d6edb25a8d4fedc8deb2711287b64c4d27719d" + } + ], + "inner": { + "fixedBytes": "0x12345678" + }, + "name": "test" +} \ No newline at end of file diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev new file mode 100644 index 000000000..9b2bd83b3 --- /dev/null +++ b/testdata/forge-std-rev @@ -0,0 +1 @@ +07263d193d621c4b2b0ce8b4d54af58f6957d97d \ No newline at end of file